/*
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 1, or (at your option)
** any later version.

** This program 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 this program; if not, write to the Free Software
** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/*
 * Author : Alexandre Parenteau <aubonbeurre@hotmail.com>
 */

#include "stdafx.h"

#include "wincvs_winutil.h"
#include "MultiString.h"
#include "wincvs.h"
#include "MainFrm.h"
#include "CustomizeMenus.h"
#include "CvsPrefs.h"
#include "CvsCommands.h"

/////////////////////////////////////////////////////////////////////////////
// helper functions declaration

CWnd* FindNextSiblingWindow(CWnd* pWnd, BOOL CheckTabStop);
CWnd* FindNextChildWindow(CWnd* pWnd, BOOL CheckTabStop);
CWnd* GetNextParentWindow(CWnd* pWnd);

CWnd* FindPrevSiblingWindow(CWnd* pWnd, BOOL CheckTabStop);
CWnd* GetPrevParentWindow(CWnd* pWnd);
CWnd* GetEndOfDecendantWindow(CWnd* pWnd);


/////////////////////////////////////////////////////////////////////////////
// inline function(s)

inline BOOL IsTargetWindow(CWnd* pWnd, BOOL CheckTabStop)
{
	return ((!CheckTabStop) || (WS_TABSTOP & pWnd->GetStyle()));
}

/////////////////////////////////////////////////////////////////////////////
// implementation

CWnd* FindNextWindow(CWnd* pWnd, BOOL CheckTabStop)
{
	ASSERT_VALID(pWnd);
	if (pWnd)
	{
		CWnd* pNextWnd;
		pNextWnd = FindNextChildWindow(pWnd, CheckTabStop);
		if (pNextWnd)
			return pNextWnd;

		pNextWnd = FindNextSiblingWindow(pWnd, CheckTabStop);
		if (pNextWnd)
			return pNextWnd;

		CWnd* pParentWnd = GetNextParentWindow(pWnd);
		while ((pParentWnd) && (pNextWnd != pWnd))
		{
			pNextWnd = FindNextChildWindow(pParentWnd, CheckTabStop);
			if (pNextWnd)
				return pNextWnd;
			pParentWnd = GetNextParentWindow(pParentWnd);
		}
	}
	return pWnd;
}

CWnd* GetNextParentWindow(CWnd* pWnd)
{
	if (pWnd)
	{
		if (pWnd->IsKindOf(RUNTIME_CLASS(CFrameWnd)))
			return pWnd->GetTopWindow();

		CFrameWnd* pFrameWnd = pWnd->GetParentFrame();
		ASSERT_VALID(pFrameWnd);
		CWnd* pParentWnd = pWnd->GetParent();
		ASSERT_VALID(pParentWnd);

		CWnd* pCrntWnd;
		BOOL bFound = FALSE;
		while ((!bFound) && (pParentWnd != pFrameWnd))
		{
			pCrntWnd = pParentWnd->GetNextWindow();
			if (pCrntWnd)
				bFound = TRUE;
			else
				pParentWnd = pParentWnd->GetParent();
		}
		return (bFound) ? pCrntWnd : pParentWnd->GetTopWindow();
	}
	return NULL;
}

CWnd* FindNextSiblingWindow(CWnd* pWnd, BOOL CheckTabStop)
{
	if (pWnd)
	{
		CWnd* pSiblingWnd = pWnd->GetNextWindow();
		while (pSiblingWnd)
		{
			if (IsTargetWindow(pSiblingWnd, CheckTabStop))
				return pSiblingWnd;
			CWnd* pChildWnd = FindNextChildWindow(pSiblingWnd, CheckTabStop);
			if (pChildWnd)
				return pChildWnd;
			pSiblingWnd = pSiblingWnd->GetNextWindow();
		}
	}
	return NULL;
}

CWnd* FindNextChildWindow(CWnd* pWnd, BOOL CheckTabStop)
{
	if (pWnd)
	{
		CWnd* pChildWnd = pWnd->GetTopWindow();
		while (pChildWnd)
		{
			if (IsTargetWindow(pChildWnd, CheckTabStop))
				return pChildWnd;
			CWnd* pSiblingWnd = FindNextSiblingWindow(pChildWnd, CheckTabStop);
			if (pSiblingWnd)
				return pSiblingWnd;
			pChildWnd = pChildWnd->GetTopWindow();
		}
	}
	return NULL;
}

CWnd* FindPrevWindow(CWnd* pWnd, BOOL CheckTabStop)
{
	ASSERT_VALID(pWnd);
	if (pWnd)
	{
		CWnd* pWndCrntLevel = pWnd;
		while (TRUE)
		{
			CWnd* pFoundWnd;
			pFoundWnd = FindPrevSiblingWindow(pWndCrntLevel, CheckTabStop);
			if (pFoundWnd)
				return pFoundWnd;
			pWndCrntLevel = GetPrevParentWindow(pWndCrntLevel);
			if (!pWndCrntLevel)
			{
				pFoundWnd = GetEndOfDecendantWindow(pWnd->GetParentFrame());
				if (IsTargetWindow(pFoundWnd, CheckTabStop))
					return pFoundWnd;
				pWndCrntLevel = pFoundWnd;
			}
		} 
	}
	return NULL;
}

CWnd* GetPrevParentWindow(CWnd* pWnd)
{
	if (pWnd)
	{
		if (!pWnd->IsKindOf(RUNTIME_CLASS(CFrameWnd)))
		{
			CWnd* pParentWnd = pWnd->GetParent();
			ASSERT_VALID(pParentWnd);
			return pParentWnd;
		}
	}
	return NULL;
}

CWnd* FindPrevSiblingWindow(CWnd* pWnd, BOOL CheckTabStop)
{
	if (pWnd)
	{
		CWnd* pSiblingWnd = pWnd->GetWindow(GW_HWNDPREV);
		while (pSiblingWnd)
		{
			if (IsTargetWindow(pSiblingWnd, CheckTabStop))
				return pSiblingWnd;
			pSiblingWnd = pSiblingWnd->GetWindow(GW_HWNDPREV);
		}
	}
	return NULL;
}

CWnd* GetEndOfDecendantWindow(CWnd* pWnd)
{
	CWnd* pTmpWnd = pWnd;
	CWnd* pPreTmpWnd;
	while (pTmpWnd)
	{
		pPreTmpWnd = pTmpWnd;
		pTmpWnd = pTmpWnd->GetWindow(GW_CHILD);
		if (pTmpWnd)
		{
			pPreTmpWnd = pTmpWnd;
			pTmpWnd = pTmpWnd->GetWindow(GW_HWNDLAST);
		}
	}
	return pPreTmpWnd;
}

static void updateUIMenu(CMenu *pMyMenu, CView* pThis)
{
	CCmdUI state;
	state.m_pMenu = pMyMenu;

	state.m_nIndexMax = pMyMenu->GetMenuItemCount();
	for (state.m_nIndex = 0; state.m_nIndex < state.m_nIndexMax;
	  state.m_nIndex++)
	{
		state.m_nID = pMyMenu->GetMenuItemID(state.m_nIndex);
		if (state.m_nID == 0)
			continue; // menu separator or invalid cmd - ignore it

		ASSERT(state.m_pOther == NULL);
		ASSERT(state.m_pMenu != NULL);
		if (state.m_nID == (UINT)-1)
		{
			// possibly a popup menu, route to first item of that popup
			state.m_pSubMenu = pMyMenu->GetSubMenu(state.m_nIndex);
			if (state.m_pSubMenu == NULL ||
				(state.m_nID = state.m_pSubMenu->GetMenuItemID(0)) == 0 ||
				state.m_nID == (UINT)-1)
			{
				continue;       // first item of popup can't be routed to
			}
			state.DoUpdate(pThis, FALSE);    // popups are never auto disabled
		}
		else
		{
			// normal menu item
			// Auto enable/disable if frame window has 'm_bAutoMenuEnable'
			//    set and command is _not_ a system command.
			state.m_pSubMenu = NULL;
			state.DoUpdate(pThis, TRUE);
		}

#	if 0 /* ??? */
		// adjust for menu deletions and additions
		UINT nCount = pMyMenu->GetMenuItemCount();
		if (nCount < state.m_nIndexMax)
		{
			state.m_nIndex -= (state.m_nIndexMax - nCount);
			while (state.m_nIndex < nCount &&
				pMyMenu->GetMenuItemID(state.m_nIndex) == state.m_nID)
			{
				state.m_nIndex++;
			}
		}
		state.m_nIndexMax = nCount;
#	endif
	}
}

/// Return the menu which has the cmd nStartId
CMenu* GetSubCMenu(int nStartId, CView* pThis)
{
	CMenu* pTopMenu = AfxGetMainWnd()->GetMenu();
	ASSERT(pTopMenu != 0L);
	if( pTopMenu == 0L )
		return NULL;

	CMenu* pMyMenu=NULL;
	for(int iPos = pTopMenu->GetMenuItemCount()-1; iPos >= 0; iPos--)
	{
		CMenu* pMenu = pTopMenu->GetSubMenu(iPos);
		if( pMenu && pMenu->GetMenuItemID(0) == (UINT)nStartId )
		{
			pMyMenu = pMenu;
			break;
		}
	}

	ASSERT(pMyMenu != 0L);
	if( pMyMenu == 0L )
		return NULL;

	updateUIMenu(pMyMenu, pThis);

	return pMyMenu;
}

CMenu* _GetMenuEntryRecurs(CMenu* menu, int nStartId, int& pos)
{
	if( menu == 0L )
		return 0l;

	for(UINT iPos = 0; iPos < menu->GetMenuItemCount(); iPos++)
	{
		UINT id = menu->GetMenuItemID(iPos);
		if( id == (UINT)nStartId )
		{
			pos = iPos;
			return menu;
		}

		if( id == (UINT)-1 )
		{
			CMenu* res = _GetMenuEntryRecurs(menu->GetSubMenu(iPos), nStartId, pos);
			if( res != 0L )
				return res;
		}
	}

	return 0L;
}

/// Return the menu and position of a cmd
CMenu* GetMenuEntry(int nStartId, int & pos)
{
	CMenu* pTopMenu = AfxGetMainWnd()->GetMenu();
	ASSERT(pTopMenu != 0L);

	return _GetMenuEntryRecurs(pTopMenu, nStartId, pos);
}

CMMenuString::CMMenuString(unsigned int maxstr, const char* uniqueName, char * const *defaultStr) :
	CPersistent(uniqueName, kNoClass), fMaxStr(maxstr)
{
	if(defaultStr != 0L)
	{
		int i = 0;
		while(defaultStr[i] != 0L)
		{
			Insert(defaultStr[i++]);
		}
	}
}

CMMenuString::~CMMenuString()
{
}

unsigned int CMMenuString::SizeOf(void) const
{
	NAMESPACE(std) vector<UStr>::const_iterator i;
	unsigned int thesize = sizeof(int);
	
	for(i = fAllStrs.begin(); i != fAllStrs.end(); ++i)
	{
		thesize += (*i).length() + 1;
	}
	return thesize;
}

const void *CMMenuString::GetData(void) const
{
	unsigned int thesize = SizeOf(), tmpsize;
	((CMMenuString *)this)->fBuf.AdjustSize(thesize);
	char *tmp = ((CMMenuString *)this)->fBuf;
	*(int *)tmp = fAllStrs.size();
	tmp += sizeof(int) / sizeof(char);
	
	NAMESPACE(std) vector<UStr>::const_iterator i;
	for(i = fAllStrs.begin(); i != fAllStrs.end(); ++i)
	{
		tmpsize = (*i).length() + 1;
		memcpy(tmp, (const char*)(*i), tmpsize * sizeof(char));
		tmp += tmpsize;
	}
	
	return (char *)fBuf;
}

void CMMenuString::SetData(const void *ptr, unsigned int size)
{
	const char* tmp = (char *)ptr;
	unsigned int thesize;
	fAllStrs.erase(fAllStrs.begin(), fAllStrs.end());
	thesize = *(int *)tmp;
	tmp += sizeof(int) / sizeof(char);
	for(unsigned int i = 0; i < thesize && i < fMaxStr; i++)
	{
		fAllStrs.push_back(UStr(tmp));
		tmp += strlen(tmp) + 1;
	}
}

void CMMenuString::Insert(const char* newstr)
{
	TouchTimeStamp();
	fAllStrs.push_back(UStr(newstr));
}

char *sDefBrowserMenu[] = 
{
	"ID_VIEW_UPDATE",
	"ID_VIEW_COMMIT",
	"ID_SEPARATOR",
	"ID_VIEW_EXPLORE",
	"ID_VIEW_QUERYUPDATE",
	"ID_VIEW_RELOAD",
	"ID_SEPARATOR",
	"ID_VIEW_IMPORT",
	"ID_VIEW_CHECKOUT",
	0L
};

char *sDefFilesMenu[] = 
{
	"ID_VIEW_UPDATE",
	"ID_VIEW_COMMIT",
	"ID_SEPARATOR",
	"ID_VIEW_EXPLORE",
	"ID_VIEW_QUERYUPDATE",
	"ID_VIEW_RELOAD",
	"ID_SEPARATOR",
	"ID_VIEW_EDITSEL",
	"ID_VIEW_EDITSELDEF",
	"ID_SEPARATOR",
	"ID_VIEW_DIFF",
	"ID_VIEW_LOG",
	"ID_VIEW_STATUS",
	"ID_VIEW_GRAPH",
	"ID_QUERY_ANNOTATE",
	0L
};

static CMMenuString gFilesMenu(99, "P_FilesMenu", sDefFilesMenu);
static CMMenuString gBrowserMenu(99, "P_BrowserMenu", sDefBrowserMenu);

/// Return the custom menu
CMenu* GetCustomMenu(int whichmenu, CView* pThis)
{
	CMMenuString* multiMenu;

	switch(whichmenu)
	{
	case kCustomBrowserMenu:
		multiMenu = &gBrowserMenu;
		break;
	case kCustomFilesMenu:
		multiMenu = &gFilesMenu;
		break;
	default:
		ASSERT(0);
		return 0L;
	}

	CMenu* res = new CMenu;
	VERIFY(res->CreatePopupMenu());

	std::vector<UStr>& list = multiMenu->GetList();
	std::vector<UStr>::iterator i = list.begin();

	while( i != list.end() )
	{
		const CustInvertTable* tab = CustGetByKey(*i);
		if( tab == 0L )
		{
			TRACE1("Removing old command(%s)\n", (const char*)*i);

			list.erase(i);
			i = list.begin();
			continue;
		}

		i++;
	}

	for(i = list.begin(); i != list.end(); ++i)
	{
		const CustInvertTable* tab = CustGetByKey(*i);
		if( tab )
		{
			if( tab->cmd == ID_SEPARATOR )
			{
				res->AppendMenu(MF_SEPARATOR);
			}
			else
			{
				CString str = tab->title;
				FormatMenuString(tab->cmd, str);
				res->AppendMenu(MF_STRING, tab->cmd, str);
			}
		}
	}

	res->AppendMenu(MF_SEPARATOR);
	res->AppendMenu(MF_STRING, whichmenu == kCustomFilesMenu ?
		ID_CUST_FILES_MENU : ID_CUST_FOLDER_MENU,
		CString("Customize this menu..."));

	updateUIMenu(res, pThis);

	return res;
}

/// Ask to customize the menus
void CustomizeMenus(int whichmenu)
{
	CMMenuString *multiMenu;

	switch(whichmenu)
	{
	case kCustomBrowserMenu:
		multiMenu = &gBrowserMenu;
		break;
	case kCustomFilesMenu:
		multiMenu = &gFilesMenu;
		break;
	default:
		ASSERT(0);
		return;
	}

	CCustomizeMenus custom(multiMenu);
	if(custom.DoModal() == IDOK)
	{
		gCvsPrefs.save();
	}
}

/*!
	Get the string for the command
	\param cmd Command
	\param str Return string for the command
	\return	TRUE if string found for the command (str contains the resulting string)
*/
bool FormatMenuString(int cmd, CString& str)
{
	bool res = false;
	
	switch(cmd)
	{
	case ID_VIEW_EDITSELDEF:
		{
			CString viever("default viewer");
			if( gCvsPrefs.Viewer() != 0L )
			{
				CStr uppath, folder;
				SplitPath(gCvsPrefs.Viewer(), uppath, folder);
				
				viever = folder.empty() ? (uppath.empty() ? gCvsPrefs.Viewer() : uppath) : folder;
				if( viever.Find(" ") > -1 )
				{
					viever = "\"" + viever + "\"";
				}
			}
			str = "View &with " + viever;
			
		}
		res = TRUE;
		break;

	default:
		break;
	}

	return res;
}

/// Get the popup menu given the popup menu string
CMenu* GetTopPopupMenu(const char* menuString)
{
	CMenu* pTopMenu = AfxGetMainWnd()->GetMenu();
	if( pTopMenu == 0L )
	{
		ASSERT(FALSE);	// No top level menu
		return NULL;
	}

	const int topMenuItemCount = (int)pTopMenu->GetMenuItemCount();
	CString popupString;
	
	for(int nIndex = 0; nIndex < topMenuItemCount; nIndex++)
	{
		pTopMenu->GetMenuString(nIndex, popupString, MF_BYPOSITION);
		if( menuString == popupString )
		{
			// Found
			return pTopMenu->GetSubMenu(nIndex);
		}
	}

	// Not found
	return NULL;
}

/*!
	Check whether Large Fonts are set in the display settings
	\return true if Large Fonts are set, false otherwise
	\note The actual testing is only done once, 
	but since OS requires a reboot after font's size change it should not be a problem
*/
bool IsLargeFonts()
{
	static bool bLargeFonts = false;
	static bool defined = false;

	if( !defined )
	{
		defined = true;
		
		HWND hDesktop = ::GetDesktopWindow();
		HDC hDesktopDC = ::GetDC(hDesktop);
		short nLogPixels = ::GetDeviceCaps(hDesktopDC, LOGPIXELSX);
		
		::ReleaseDC(hDesktop, hDesktopDC);
		
		bLargeFonts = (nLogPixels > 96);
	}

	return bLargeFonts;
}

/*!
	Check whether the hide command dialog key is pressed
	\return true if hiding key pressed, false otherwise
	\note The hiding key is determined in the preferences and it can be either Shift or Ctrl
*/
bool IsHideCommandKeyPressed()
{
	return ::GetKeyState(gCvsPrefs.HideCommandDlgUseShift() ? VK_SHIFT : VK_CONTROL) < 0;
}

/*!
	Set the extended style for the list control
	\param pListCtrl List control to set the style for
	\param dwExtendedStyle
	\return The previous extended style of the list control
*/
DWORD SetListCtrlExtendedStyle(CListCtrl* pListCtrl, const DWORD dwExtendedStyle)
{
	if( !pListCtrl )
	{
		ASSERT(FALSE);	// bad pointer
		return 0;
	}

	return pListCtrl->SetExtendedStyle(pListCtrl->GetExtendedStyle() | dwExtendedStyle);
}

//////////////////////////////////////////////////////////////////////////
// CWndRedrawManager

/// Constructor sets the redraw to FALSE unless overrriden by bRedraw flag
CWndRedrawMngr::CWndRedrawMngr(CWnd* pWnd, BOOL bRedraw /*= FALSE*/)
{
	ASSERT(pWnd != NULL);
	m_pWnd = pWnd;
	
	if( m_pWnd )
	{
		m_pWnd->SetRedraw(bRedraw);
	}
}

/// Destructor will make sure that Redraw is set to TRUE when object goes out of scope
CWndRedrawMngr::~CWndRedrawMngr()
{
	if( m_pWnd )
	{
		m_pWnd->SetRedraw(TRUE);
	}
}

/*!
	Basic manipulation of the Redraw state
	\param bRedraw New value for the Redraw state
*/
void CWndRedrawMngr::SetRedraw(BOOL bRedraw)
{
	if( m_pWnd )
	{
		m_pWnd->SetRedraw(bRedraw);
	}
}

//////////////////////////////////////////////////////////////////////////
// CCvsThreadLock

CRITICAL_SECTION CCvsThreadLock::m_lock;
bool CCvsThreadLock::m_initialized = false;

CCvsThreadLock::CCvsThreadLock()
{
	EnterCriticalSection(&m_lock);
}

CCvsThreadLock::~CCvsThreadLock()
{
	LeaveCriticalSection(&m_lock);
}

/*!
	Initialize critical section
*/
void CCvsThreadLock::Initialize()
{
	if( !m_initialized )
	{
		m_initialized = true;
		InitializeCriticalSection(&m_lock);
	}
}

/*!
	Delete critical section
*/
void CCvsThreadLock::Delete()
{
	if( m_initialized )
	{
		DeleteCriticalSection(&m_lock);
		m_initialized = false;
	}
}
