/*
** 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 : Jerzy Kaczorowski <kaczoroj@hotmail.com> --- July 2001
 */

// HistComboBoxEx.cpp : implementation file
//

#include "stdafx.h"
#include "wincvs.h"
#include "HistComboBoxEx.h"
#include "FileTraversal.h"
#include "CvsEntries.h"

#include "MainFrm.h"
#include "CvsAlert.h"
#include "CvsCommands.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

static PCPStr gOldLoc("P_BrowserLoc", "C:\\");

int TraverseHistory(CHistComboBoxEx& browseHist)
{
	char szPath[MAX_PATH] = "";
	for( int nIndex = 0; nIndex < browseHist.GetCount(); nIndex++ )
	{
		COMBOBOXEXITEM cbi;
		cbi.mask = CBEIF_TEXT;
		cbi.iItem = nIndex;
		cbi.pszText = szPath;
		cbi.cchTextMax = MAX_PATH;
		
		browseHist.GetItem(&cbi);
		browseHist.SetIcon(cbi);
	}

	return nIndex;
}

/////////////////////////////////////////////////////////////////////////////
// CHistComboBoxEx

CHistComboBoxEx::CHistComboBoxEx(int feature /*= USmartCombo::DefaultFeature*/)
	: USmartCombo(feature)
{
}

CHistComboBoxEx::~CHistComboBoxEx()
{
}


BEGIN_MESSAGE_MAP(CHistComboBoxEx, CComboBoxEx)
	//{{AFX_MSG_MAP(CHistComboBoxEx)
	ON_CONTROL_REFLECT(CBN_DROPDOWN, OnDropdown)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CHistComboBoxEx message handlers

const char* CHistComboBoxEx::GetOldLoc()
{
	return gOldLoc;
}

int CHistComboBoxEx::NewRoot(const char* root)
{
	if(stricmp(gOldLoc, root) != 0)
		gOldLoc = root;

	CMString* pstrItems = GetItems();
	ASSERT(pstrItems != NULL);

	// update the history combo
	pstrItems->Insert(root);
	ResetContent();
	const std::vector<CStr> & list = pstrItems->GetList();
	std::vector<CStr>::const_iterator i;
	for(i = list.begin(); i != list.end(); ++i)
	{
		InsertString(-1, *i);
	}
	SetCurSel(0);
	TRACE("Browse History has %d items\n", GetCount());

	return GetCount();
}

///Find the best root for passed path
CString CHistComboBoxEx::FindBestRoot(CMString& strItems, const char* path, bool mustExist /*= false*/)
{
	CString normPath(path);
	CString bestRoot;
	normPath.TrimRight("\\/");
	normPath.MakeLower();

	const std::vector<CStr> & list = strItems.GetList();
	std::vector<CStr>::const_iterator i;
	for(i = list.begin(); i != list.end(); ++i)
	{
		CString root = *i;
		root.TrimRight("\\/");
		root.MakeLower();

		if( normPath.Find(root) != -1 )
		{
			//we want the longest root so it's near the file
			if( root.GetLength() > bestRoot.GetLength() )	
			{
				bestRoot = *i;
			}
		}
	}

	if( !mustExist && bestRoot.IsEmpty() )
	{
		bestRoot = path;
		bestRoot.TrimRight("\\/");
	}

	//handle the drive root
	if( bestRoot.GetAllocLength() &&
		':' == bestRoot[bestRoot.GetLength()-1])
	{
		bestRoot += kPathDelimiter;
	}
	
	return bestRoot;
}

int CHistComboBoxEx::InsertString(int nIndex, LPCTSTR lpszString)
{
	int nRes = -1;

	COMBOBOXEXITEM cbi;
	cbi.mask = CBEIF_TEXT;
	cbi.iItem = 0 > nIndex ? GetCount() : nIndex;
	cbi.pszText = (LPTSTR)lpszString;	//this cast is safe because we are Inserting item only
	cbi.cchTextMax = 0;

	nRes = InsertItem(&cbi);
	SetIcon(cbi);

	return nRes;
}

int CHistComboBoxEx::AddString(LPCTSTR lpszString)
{
	return InsertString(-1, lpszString);
}

BOOL CHistComboBoxEx::SetIcon(COMBOBOXEXITEM cbi)
{
	//initialize the image list if neccessary
	if( m_ImageList.m_hImageList == 0L )
	{
		m_ImageList.Create(IDB_HISTLOC, HISTLOC_IMAGE_CX, 1, RGB(255, 255, 255));
		SetImageList(&m_ImageList);
	}
	
	cbi.mask = CBEIF_IMAGE | /*CBEIF_OVERLAY |*/ CBEIF_SELECTEDIMAGE;
	cbi.iImage = cbi.iSelectedImage = kLocationIconUnknown;
	//cbi.iOverlay = ;

	CSortList<ENTNODE> entries(200, ENTNODE::Compare);
	vector<CStr> ignlist;
	struct stat sb;
	const char *fullpath = cbi.pszText;
	
	int statres = stat(fullpath, &sb);
	if( statres != -1 )
	{
		if( S_ISDIR(sb.st_mode) )
		{
			CStr uppath, folder;
			if( !SplitPath(fullpath, uppath, folder) || uppath.empty() )
			{
				uppath = fullpath;
				folder = ".";
			}
			
			BuildIgnoredList(ignlist, uppath);

			EntnodeData *data = Entries_SetVisited(uppath, entries, folder, sb, true, &ignlist);
			if( !data->IsUnknown() )
			{
				cbi.iImage = cbi.iSelectedImage = kLocationIcon;
			}
			if( data->IsIgnored() )
			{
				cbi.iImage = cbi.iSelectedImage = kLocationIconIgnored;
			}
			if( data->IsMissing() )
			{
				cbi.iImage = cbi.iSelectedImage = kLocationIconMissing;
			}
		}
	}
	else
	{
		cbi.iImage = cbi.iSelectedImage = kLocationIconErased;
	}
	
	return SetItem(&cbi);
}

/*! \brief PreTranslateMessage virtual override.
 * 
 * If the USmartCombo::RemoveItems feature is set perform the key test to determine whether to delete item.
 */
BOOL CHistComboBoxEx::PreTranslateMessage(MSG* pMsg) 
{
	if( HasFeature(USmartCombo::RemoveItems) )
	{
		if( DelKeyTest(pMsg) )
		{
			return TRUE;
		}
	}

	return CComboBoxEx::PreTranslateMessage(pMsg);
}

/*! \brief CBN_DROPDOWN notification message handler.
 * 
 * Traverse the history items to set the apropriate icons and calculates the width of the droplist if 
 * the USmartCombo::AutoDropWidth feature is set.
 */
void CHistComboBoxEx::OnDropdown() 
{
	TraverseHistory(*this);
	if( HasFeature(USmartCombo::AutoDropWidth) )
	{
		SetDroppedWidth(CalcDroppedWidth());
	}
}

/*! \brief Calculate droplist width.
 * 
 * Calculate the proper width for droplist so the items can fit into it. 
 * Make sure it doesn't go off-screen as well.
 */
int CHistComboBoxEx::CalcDroppedWidth()
{
	int maxSize = 0;

	CClientDC ClientDC(this);
	int savedDc = ClientDC.SaveDC();
    ClientDC.SelectObject(GetFont());
	
	CString strLBText;
	for( int nIndex = 0; nIndex < GetCount(); nIndex++ )
	{
		GetLBText( nIndex, strLBText );
		CSize size = ClientDC.GetTextExtent(strLBText);
		
		if( size.cx > maxSize )
		{
			maxSize = size.cx;
		}
	}
	ClientDC.RestoreDC(savedDc);

	maxSize += ::GetSystemMetrics(SM_CXVSCROLL)		//vertical scroll bar
		+ 4*::GetSystemMetrics(SM_CXEDGE)			//the border aroung the text and icon: Edge-Icon-Edge - Edge-Text-Edge
		+ HISTLOC_IMAGE_CX							//the icon width
		+ 2;										//the focus frame width
	
	CRect rect;
	GetWindowRect(rect);
	maxSize = min(ClientDC.GetDeviceCaps(HORZRES) - rect.TopLeft().x, maxSize);

	return maxSize;
}

/*! \brief Test if the Del key is pressed and remove the item if the droplist is dropped down.
 *	
 * Analizes the message and removes the item if the Del key is pressed while droplist is dropped down.
 * \attention Make sure the items collection is set earlier (call USmartCombo::SetItems to set the collection).
 * \return TRUE if the droplist was dropped and Del key was pressed.
 */
BOOL CHistComboBoxEx::DelKeyTest(MSG* pMsg)
{
	CMString* pstrItems = GetItems();
	ASSERT(pstrItems != NULL);

	BOOL bRes = FALSE;

	if( WM_KEYDOWN == pMsg->message && 
		VK_DELETE == (int)pMsg->wParam && 
		pstrItems && 
		GetDroppedState() )
	{
		int sel = GetCurSel();
		if( CB_ERR < sel && GetCount() > 1 )
		{
			CString strItem;
			GetLBText(sel, strItem);

			if( RemoveItem(strItem) )
			{
				DeleteString(sel);
				
				if( GetCount() )
				{
					int nNewSel = (0 == sel ? sel : (GetCount() ? 0 : -1));
					if( 0 != nNewSel || 0 == sel )
					{
						SetCurSel(nNewSel);
						
						//trigger the location change for all views
						CWincvsApp* app = (CWincvsApp *)AfxGetApp();
						CMainFrame* mainFrm = app->GetMainFrame();
						mainFrm->ChangeLocation();
					}
				}
			}
		}
		else if( GetCount() == 1 )
		{
			AfxMessageBox("Can't remove the last item from the history.\n"
				"Please add a new location first and then remove the current one.", MB_OK | MB_ICONSTOP);
		}

		//we have to prevent it going to another window
		bRes = TRUE;
	}

	return bRes;
}
