/*
** 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> (Originally: Sebastien Abras <sabdev@ibelgique.com>) --- July 2002
 */

// ItemListDlg.cpp : implementation file
//

#include "stdafx.h"

#ifdef WIN32
#include "wincvs.h"
#include "wincvsView.h"
#include "wincvs_winutil.h"
#endif /* !WIN32 */

#include "ItemListDlg.h"

#include "cvsargs.h"
#include "cvsgui_process.h"


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


#define STR_ITEMLIST_DIR		"<Dir>"
#define STR_ITEMLIST_UP_DIR		".."
#define STR_ITEMLIST_BRANCH		"Branch"
#define STR_ITEMLIST_REVISION	"Revision"

/// Persistent <b>recurse</b> setting for tag/branch browse
CPersistentBool gItemListTagRecurse("P_ItemListTagRecurse", false);

/// Persistent <b>list</b> setting for module/directory-file browse
CPersistentBool gItemListModuleList("P_ItemListModuleList", false);

/// Months table used to parse the date string
string gStrMonths[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

//////////////////////////////////////////////////////////////////////////
// CItemListConsole

CItemListConsole::CItemListConsole()
{
}

CItemListConsole::~CItemListConsole()
{
	m_itemList.clear();
}

long CItemListConsole::cvs_out(char*txt, long len)
{
	m_cvsResult << txt;
	return len;
}

long CItemListConsole::cvs_err(char* txt, long len)
{
	cvs_errstr(txt, len);
	return len;
}

/*!
	Virtual overridable to parse the result of browse command
	\note Call default implementation after parsing is finished to clear the stored command output
*/
void CItemListConsole::Parse()
{
	m_cvsResult.clear();
}

/// Get the parsing result
const ItemList& CItemListConsole::GetItemList() const
{
	return m_itemList;
}

//////////////////////////////////////////////////////////////////////////
// CTagParserConsole

CTagParserConsole::CTagParserConsole()
{
}

CTagParserConsole::~CTagParserConsole()
{
}

void CTagParserConsole::Parse()
{
	bool btag = false;
	string sz;
	
	while( getline(m_cvsResult, sz) )
	{
		if( btag )
		{
			if( sz.size() == 0 || sz.find("keyword substitution:") != -1 )
			{
				btag = false;
			}
			else
			{
				stringstream stream;
				string tag;
				
				stream << sz;
				stream >> tag;
				
				tag.erase(tag.find(':'), tag.size());
				
				if( !tag.empty() )
				{
					ItemRowInfo rowInfo;
					string type;
					
					stream >> type;
					
					if( type.size() > 0 )
					{
						bool isBranch = false;
						
						const int dotsCount = count(type.begin(), type.end(), '.');
						if( dotsCount > 2 && dotsCount % 2 )
						{
							// If we have even and greater than two number of separated integers
							// then check if it's a magic branch
							string magicTest = type;
							magicTest.erase(magicTest.find_last_of('.'), magicTest.size());
							magicTest.erase(0, magicTest.find_last_of('.') + 1);
							
							if( !magicTest.empty() && magicTest[0] == '0' )
							{
								isBranch = true;
							}
						}
						else if( dotsCount > 1 && !(dotsCount % 2) )
						{
							// We have an odd and greater than one number of integers, surely a branch
							isBranch = true;
						}
						
						// Set the type apropriate to findings
						type = isBranch ? STR_ITEMLIST_BRANCH : STR_ITEMLIST_REVISION;
					}
					
					ItemList::iterator i = m_itemList.find(tag);
					if( i != m_itemList.end() )
					{
						if( !type.empty() )
						{
							(*i).second.m_description.insert(type);
						}
					}
					else
					{
						if( !type.empty() )
						{
							rowInfo.m_description.insert(type);
						}
						
						m_itemList.insert(std::make_pair(tag, rowInfo));
					}
				}
			}
		}
		
		if( sz.find("symbolic names:") != -1 )
		{
			btag = true;
		}
	}
	
	CItemListConsole::Parse();
}

//////////////////////////////////////////////////////////////////////////
// CModuleParserConsole

CModuleParserConsole::CModuleParserConsole()
{
}

CModuleParserConsole::~CModuleParserConsole()
{
}

void CModuleParserConsole::Parse()
{
	string sz;
	
	while( getline(m_cvsResult, sz) )
	{
		if( sz.size() > 0 && !isspace(sz.c_str()[0]) )
		{
			stringstream stream;
			string module;
			
			stream << sz;
			stream >> module;
			
			if( module.size() != 0 )
			{
				ItemRowInfo rowInfo;
				m_itemList.insert(std::make_pair(module, rowInfo));
			}
		}
	}
	
	CItemListConsole::Parse();
}

//////////////////////////////////////////////////////////////////////////
// CListParserConsole

CListParserConsole::CListParserConsole()
{
}

CListParserConsole::~CListParserConsole()
{
}

void CListParserConsole::Parse()
{
	string sz;
	
	while( getline(m_cvsResult, sz) )
	{
		if( sz.size() > 0 && !isspace(sz.c_str()[0]) )
		{
			const char entrySeparator = '/';
			string entryType;
			stringstream line(sz);
			
			if( getline(line, entryType, entrySeparator) )
			{
				if( entryType.size() > 0 && entryType.c_str()[0] == 'D' )
				{
					string directory;
					
					if( getline(line, directory, entrySeparator) && !directory.empty() )
					{
						ItemRowInfo rowInfo;
						rowInfo.m_description.insert(STR_ITEMLIST_DIR);
						
						m_itemList.insert(std::make_pair(directory, rowInfo));
					}
				}
				else
				{
					string file;
					
					if( getline(line, file, entrySeparator) && !file.empty() )
					{
						string revision;
						string date;
						
						if( getline(line, revision, entrySeparator) && 
							getline(line, date, entrySeparator) )
						{
							ItemRowInfo rowInfo;
							rowInfo.m_description.insert(revision);
							rowInfo.m_descriptionEx.insert(date);
							
							m_itemList.insert(std::make_pair(file, rowInfo));
						}
					}
				}
			}
		}
	}
	
	CItemListConsole::Parse();
}


/*!
	Format an item's description string given a collection of descriptions
	\param itemDesc collection of descriptions
	\return Formatted item's description string 
*/
static string FormatItemDescription(const ItemDescription& itemDesc)
{
	string strRes;

	for(ItemDescription::const_iterator it = itemDesc.begin(); it != itemDesc.end(); it++)
	{
		if( !strRes.empty() )
		{
			strRes += ", ";
		}

		strRes += (*it).c_str();
	}

	return strRes;
}

/*!
	Build the path string given a list of items
	\param listPath List of items to be in the path
	\param pathDelimiter Optional path delimiter to be used
	\return The formatted path as string
	\note Path ends with path delimiter
*/
static string FormatListPath(const ListPath& listPath, const char pathDelimiter = '/')
{
	string strRes;

	for(ListPath::const_iterator it = listPath.begin(); it != listPath.end(); it++ ) 
	{
		strRes += (*it).c_str();
		strRes += pathDelimiter;
	}

	return strRes;
}

/*!
	Launch and parse cvs command apropriate to the type and parameters
	\return The pointer to item list console
	\note You have to free the memory allocated for the item list console
*/
static CItemListConsole* LaunchItemListCommand(kItemListDlg type, bool option, 
											   const char* readFrom, bool forceCvsroot, 
											   const char* revision, const char* date,
											   const ListPath* listPath = NULL)
{
	CItemListConsole* itemListConsole = NULL;

	CvsArgs args;
	MultiFiles mf;

	if( forceCvsroot )
	{
		args.addcvsroot();
	}

	switch( type )
	{
	case kItemListTag:
		itemListConsole = new CTagParserConsole;
		
		args.add(forceCvsroot ? "rlog" : "log");
		args.add("-h");
		
		if( !option )
		{
			args.add("-l");
		}

		if( strlen(revision) )
		{
			string strRevision;
			
			strRevision += "-r";
			strRevision += revision;

			args.add(strRevision.c_str());
		}
		
		if( strlen(date) )
		{
			args.add("-d");
			args.add(date);
		}

		{
			CStr uppath, file;
			SplitPath(readFrom, uppath, file, true);
			
			mf.newdir(uppath);
			
			if( !file.empty() )
			{
				mf.newfile(file);
			}
		}
		break;
	case kItemListModule:
		if( option )
		{
			itemListConsole = new CListParserConsole;

			args.add("ls");
			args.add("-e");

			if( strlen(revision) )
			{
				args.add("-r");
				args.add(revision);
			}

			if( strlen(date) )
			{
				args.add("-D");
				args.add(date);
			}

			if( listPath )
			{
				string strListPath = FormatListPath(*listPath);
				if( !strListPath.empty() )
				{
					args.add(strListPath.c_str());
				}
			}
		}
		else
		{
			itemListConsole = new CModuleParserConsole;

			args.add("checkout");
			args.add("-c");
		}

		mf.newdir(readFrom);
		break;
	default:
#ifdef WIN32
		ASSERT(FALSE);	// unknown option
#endif
		break;
	}

	if( itemListConsole )
	{
		mf.next();
		const char* dir = mf.add(args);
		
		args.print(dir);
		launchCVS(dir, args.Argc(), args.Argv(), itemListConsole);
		itemListConsole->Parse();
	}

	return itemListConsole;
}

/*!
	Extract the date-time information from the string
	\param strTime Date-time string as returned by <b>cvs ls -e</b> command (CVS/Entries file format)
	\return The date-time as time_t value or (time_t)-1 as returned by mktime
*/
static time_t GetDateTime(const char* strTime)
{
	struct tm tmTime = { 0 };

	string stringTime = strTime;
	if( stringTime.empty() )
	{
		return -1;
	}

	// Replace the time separator with spaces to help with parsing
	replace(stringTime.begin(), stringTime.end(), ':', ' ');

	// Parse
	stringstream parser;
	parser << stringTime;
	
	string strDay;
	string strMonth;

	parser >> strDay;
	parser >> strMonth;

	{
		// Find out the month
		tmTime.tm_mon = -1;
		string* month = find(gStrMonths, gStrMonths + 12, strMonth);

		if( month != gStrMonths + 12 )
		{
			tmTime.tm_mon = month - gStrMonths;
		}
		else
		{
#ifdef WIN32
			ASSERT(FALSE);	// unknown month
#endif
			return -1;
		}
	}

	parser >> tmTime.tm_mday;
	parser >> tmTime.tm_hour;
	parser >> tmTime.tm_min;
	parser >> tmTime.tm_sec;
	
	parser >> tmTime.tm_year;
	tmTime.tm_year -= 1900;

    tmTime.tm_isdst = -1;

	return mktime(&tmTime);
}

#ifdef WIN32

//////////////////////////////////////////////////////////////////////////
// ItemDisplayInfo

/*!
	Create display info
	\param item Item
	\param rowInfo Row info for the item
*/
void ItemDisplayInfo::Create(const string& item, const ItemRowInfo& rowInfo)
{
	m_item = item;
	m_description = FormatItemDescription(rowInfo.m_description);
	m_descriptionEx = FormatItemDescription(rowInfo.m_descriptionEx);

	m_rowInfo = rowInfo;
}

/// Get item text
const string& ItemDisplayInfo::GetItem() const
{
	return m_item;
}

/// Get the description text
const string& ItemDisplayInfo::GetDescription() const
{
	return m_description;
}

/// Get the extended desription text
const string& ItemDisplayInfo::GetDescriptionEx() const
{
	return m_descriptionEx;
}

/// Get the row info
const ItemRowInfo& ItemDisplayInfo::GetRowInfo() const
{
	return m_rowInfo;
}

/*!
	Callback method used for sorting of items in the list control
	\param itemData1 ItemDisplayInfo* of the first element
	\param itemData2 ItemDisplayInfo* of the second element
	\param lParam CompareInfo* Sort settings
	\return 1, -1 or 0 depending on the sort settings
*/
int CALLBACK CItemListDlg::CompareItems(LPARAM itemData1, LPARAM itemData2, LPARAM lParam)
{
	int res = 0;

	const CompareInfo* info = (CompareInfo*)lParam;
	if( info )
	{
		ItemDisplayInfo* displayInfo1 = (ItemDisplayInfo*)itemData1;
		ItemDisplayInfo* displayInfo2 = (ItemDisplayInfo*)itemData2;
		
		if( displayInfo1 && displayInfo2 )
		{
			switch( info->m_type )
			{
			case kItemListTag:
				switch( info->m_column )
				{
				case 0:
					res = stricmp(displayInfo1->GetItem().c_str(), displayInfo2->GetItem().c_str());
					break;
				case 1:
					res = stricmp(displayInfo1->GetDescription().c_str(), displayInfo2->GetDescription().c_str());
					break;
				default:
					ASSERT(FALSE); // should not come here
					break;
				}
				break;
			case kItemListModule:
				if( info->m_option )
				{
					if( strcmp(displayInfo1->GetItem().c_str(), STR_ITEMLIST_UP_DIR) == 0 )
					{
						res = info->m_asc ? 1 : -1;
					}
					else if( strcmp(displayInfo2->GetItem().c_str(), STR_ITEMLIST_UP_DIR) == 0 )
					{
						res = info->m_asc ? -1 : 1;
					}
					else
					{
						bool isDir1 = strcmp(displayInfo1->GetDescription().c_str(), STR_ITEMLIST_DIR) == 0;
						bool isDir2 = strcmp(displayInfo2->GetDescription().c_str(), STR_ITEMLIST_DIR) == 0;

						if( isDir1 && !isDir2 )
						{
							res = info->m_asc ? 1 : -1;
						}
						else if( !isDir1 && isDir2 )
						{
							res = info->m_asc ? -1 : 1;
						}
						else if( isDir1 && isDir2 )
						{
							res = stricmp(displayInfo1->GetItem().c_str(), displayInfo2->GetItem().c_str());
							if( info->m_column != 0 )
							{
								if( info->m_asc )
								{
									res *= -1;
								}
							}
						}
						else
						{
							switch( info->m_column )
							{
							case 0:
								res = stricmp(displayInfo1->GetItem().c_str(), displayInfo2->GetItem().c_str());
								break;
							case 1:
								res = stricmp(displayInfo1->GetDescription().c_str(), displayInfo2->GetDescription().c_str());
								break;
							case 2:
								{
									const time_t time1 = GetDateTime(displayInfo1->GetDescriptionEx().c_str()); 
									const time_t time2 = GetDateTime(displayInfo2->GetDescriptionEx().c_str()); 
									
									if( time1 != (time_t)-1 && time2 != (time_t)-1 ) 
									{ 
										res = time1 < time2 ? -1 : (time1 > time2 ? 1 : 0);
									} 
									else
									{
										res = stricmp(displayInfo1->GetDescriptionEx().c_str(), displayInfo2->GetDescriptionEx().c_str());
									}
								}
								break;
							default:
								ASSERT(FALSE); // should not come here
								break;
							}
						}
					}
				}
				else
				{
					switch( info->m_column )
					{
					case 0:
						res = stricmp(displayInfo1->GetItem().c_str(), displayInfo2->GetItem().c_str());
						break;
					default:
						ASSERT(FALSE); // should not come here
						break;
					}
				}

				break;
			default:
				ASSERT(FALSE);	// unknown type
				break;
			}
		}
	}

	if( res )
	{
		if( info->m_asc )
		{
			res *= -1;
		}
	}

	return res;
}


/////////////////////////////////////////////////////////////////////////////
// CItemListDlg dialog


CItemListDlg::CItemListDlg(const MultiFiles* mf,
						   const char* item, kItemListDlg type, ItemList& itemList, const ListPath* listPath,
						   bool forceCvsroot, const char* rev, const char* date, 
						   CWnd* pParent /*=NULL*/)
						   : m_mf(mf), m_itemList(itemList), m_readFromCombo(USmartCombo::AutoDropWidth),
						   CDialog(CItemListDlg::IDD, pParent)
{
	//{{AFX_DATA_INIT(CItemListDlg)
	m_readFrom = _T("");
	m_item = _T("");
	m_option = FALSE;
	m_forceCvsroot = FALSE;
	m_hasRev = FALSE;
	m_rev = _T("");
	m_date = _T("");
	m_hasDate = FALSE;
	//}}AFX_DATA_INIT

	m_item = item;

	m_type = type;

	switch( m_type )
	{
	case kItemListTag:
		m_option = (bool)gItemListTagRecurse;
		break;
	case kItemListModule:
		m_option = (bool)gItemListModuleList;
		break;
	default:
		// Nothing to do
		break;
	}

	if( listPath )
	{
		m_listPath = *listPath;
	}

	m_forceCvsroot = forceCvsroot;
	m_rev = rev;
	m_date = date;

	m_sortAsc = false;
	m_sortColumn = 0;

	m_revCombo.SetItems(&gRevNames);
	m_dateCombo.SetItems(&gDateNames);
}


void CItemListDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CItemListDlg)
	DDX_Control(pDX, IDC_CHECKDATE, m_hasDateCheck);
	DDX_Control(pDX, IDC_CHECKREV, m_hasRevCheck);
	DDX_Control(pDX, IDC_FORCEROOT, m_forceCvsrootCheck);
	DDX_Control(pDX, IDC_COMBODATE, m_dateCombo);
	DDX_Control(pDX, IDC_COMBOREV, m_revCombo);
	DDX_Control(pDX, IDC_OPTION, m_optionCheck);
	DDX_Control(pDX, IDC_ITEMLIST, m_itemListCtrl);
	DDX_Control(pDX, IDC_READFROM, m_readFromCombo);
	DDX_CBString(pDX, IDC_READFROM, m_readFrom);
	DDX_Text(pDX, IDC_ITEM, m_item);
	DDX_Check(pDX, IDC_OPTION, m_option);
	DDX_Check(pDX, IDC_FORCEROOT, m_forceCvsroot);
	DDX_Check(pDX, IDC_CHECKREV, m_hasRev);
	DDX_CBString(pDX, IDC_COMBOREV, m_rev);
	DDX_CBString(pDX, IDC_COMBODATE, m_date);
	DDX_Check(pDX, IDC_CHECKDATE, m_hasDate);
	//}}AFX_DATA_MAP

	DDX_ComboMString(pDX, IDC_COMBOREV, m_revCombo);
	DDX_ComboMString(pDX, IDC_COMBODATE, m_dateCombo);

	if( pDX->m_bSaveAndValidate )
	{
		m_itemList.clear();
		
		for(int nIndex = 0; nIndex < m_itemListCtrl.GetItemCount(); nIndex++)
		{
			ItemDisplayInfo* displayInfo = (ItemDisplayInfo*)m_itemListCtrl.GetItemData(nIndex);
			if( displayInfo )
			{
				m_itemList.insert(std::make_pair(displayInfo->GetItem(), displayInfo->GetRowInfo()));
			}
		}
	}
}


BEGIN_MESSAGE_MAP(CItemListDlg, CDialog)
	//{{AFX_MSG_MAP(CItemListDlg)
	ON_BN_CLICKED(IDC_REFRESH, OnRefresh)
	ON_BN_CLICKED(IDC_OPTION, OnOption)
	ON_BN_CLICKED(IDC_CHECKREV, OnCheckrev)
	ON_BN_CLICKED(IDC_CHECKDATE, OnCheckdate)
	ON_BN_CLICKED(IDC_BROWSE_TAG, OnBrowseTag)
	ON_NOTIFY(NM_DBLCLK, IDC_ITEMLIST, OnDblclkItemlist)
	ON_NOTIFY(LVN_ITEMCHANGED, IDC_ITEMLIST, OnItemchangedItemlist)
	ON_NOTIFY(LVN_COLUMNCLICK, IDC_ITEMLIST, OnColumnclickItemlist)
	ON_WM_DESTROY()
	ON_BN_CLICKED(IDC_FORCEROOT, OnForceroot)
	ON_BN_CLICKED(IDC_BROWSE_READFROM, OnBrowseReadfrom)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CItemListDlg message handlers

/// OnInitDialog virtual override, initializes controls and triggers data reading if neccessary
BOOL CItemListDlg::OnInitDialog() 
{
	CDialog::OnInitDialog();
	
	// Extra initialization
	{
		DWORD dwExtendedStyle = m_itemListCtrl.GetExtendedStyle();
		dwExtendedStyle |= LVS_EX_FULLROWSELECT;
		m_itemListCtrl.SetExtendedStyle(dwExtendedStyle);
	}

	// Setup controls
	CHeaderCtrl* headerCtrl = m_itemListCtrl.GetHeaderCtrl();
	if( headerCtrl )
	{
		m_headerCtrl.SubclassWindow(headerCtrl->m_hWnd);
	}

	InitListCtrl();
	InitOptionCtrls();
	OnForceroot();

	m_ItemsImageList.Create(IDR_ITEMLIST, 16, 1, RGB(255, 0, 255));
	m_itemListCtrl.SetImageList(&m_ItemsImageList, LVSIL_SMALL);

	int nReadFromSelection = 0;

	if( m_mf )
	{
		// Add selected directories
		for( int nIndex = 0; nIndex < m_mf->NumDirs(); nIndex++ )
		{
			CStr path;
			if( m_mf->getdir(nIndex, path) )
			{
				m_readFromCombo.AddString(CString(path));
			}
		}
	}
	
	switch( m_type )
	{
	case kItemListTag:
		// Set windows titles
		SetWindowText("Select tag/branch");
		SetDlgItemText(IDC_ITEMLABEL, "&Tag/branch:");
		SetDlgItemText(IDC_OPTION, "&Recurse");

		// The browser selection
		if( m_mf )
		{
			// Add selected files
			for( int nIndex = 0; nIndex < m_mf->NumFiles(); nIndex++ )
			{
				UStr path, fileName, currRev;
				if( m_mf->get(nIndex, path, fileName, currRev) )
				{
					if( !path.endsWith(kPathDelimiter) )
					{
						path << kPathDelimiter;
					}

					path << fileName;

					int nComboIndex = m_readFromCombo.AddString(path);
					
					// It's better to have a single file selected initially for the performance reasons
					if( 0 == nReadFromSelection )
					{
						nReadFromSelection = nComboIndex;
					}
				}
			}
		}
		
		break;
	case kItemListModule:
		// Set windows titles
		SetWindowText("Select module");
		SetDlgItemText(IDC_ITEMLABEL, "&Module:");
		SetDlgItemText(IDC_OPTION, "&List");

		GetDlgItem(IDC_BROWSE_READFROM)->ShowWindow(SW_HIDE);
		break;
	default:
		ASSERT(FALSE);	// unknown type
		EndDialog(IDCANCEL);
		return FALSE;
	}
	
	// Set the "read from"
	if( m_readFromCombo.GetCount() )
	{
		m_readFromCombo.SetCurSel(nReadFromSelection);
	}

	// Trigger refresh
	if( m_itemList.empty() )
	{
		// Force the window to show itself
		ShowWindow(SW_SHOW);

		// "Click" the refresh button
		PostMessage(WM_COMMAND, MAKEWPARAM(IDC_REFRESH, BN_CLICKED), (LPARAM)NULL);
	}
	else
	{
		DisplayItems(m_itemList, false);
		SortItems(m_sortColumn, m_sortAsc, m_type, m_optionCheck.GetCheck() != 0);

		if( !m_item.IsEmpty() )
		{
			SelectItem(m_item);
		}
	}

	return TRUE;  // return TRUE unless you set the focus to a control
	              // EXCEPTION: OCX Property Pages should return FALSE
}

/*!
	Set the list control columns apropriate to the dialog type, show prompt message eventually
	\param readingData TRUE if data are being read, FALSE otherwise
	\note Prompt message will be shown while data are being read
*/
void CItemListDlg::InitListCtrl(BOOL readingData /*= FALSE*/)
{
	CWndRedrawMngr redrawMngr(&m_itemListCtrl);

	// Remove all items and columns
	DeleteAllItems();

	int columnCount = m_itemListCtrl.GetHeaderCtrl()->GetItemCount();
	while(columnCount--)
	{
		m_itemListCtrl.DeleteColumn(0);
	}

	// Get the client area width to properly size columns
	CRect rect;
	m_itemListCtrl.GetClientRect(rect);

	const int listCtrlWidth = rect.Width();

	// Set the apropriate columns
	if( readingData )
	{
		m_itemListCtrl.InsertColumn(0, "Action", LVCFMT_LEFT, listCtrlWidth);

		m_itemListCtrl.InsertItem(0, "Please wait while retrieving data", kItemImgBlank);
		m_itemListCtrl.InsertItem(1, "You can press Stop button to cancel the operation", kItemImgBlank);
	}
	else
	{
		const int listCtrlWidthNoScroll = listCtrlWidth - GetSystemMetrics(SM_CXVSCROLL);

		switch( m_type )
		{
		case kItemListTag:
			m_itemListCtrl.InsertColumn(0, "Tag/branch", LVCFMT_LEFT, 200);
			m_itemListCtrl.InsertColumn(1, "Description", LVCFMT_LEFT, listCtrlWidthNoScroll - 200);
			break;
		case kItemListModule:
			m_itemListCtrl.InsertColumn(0, "Module", LVCFMT_LEFT, m_optionCheck.GetCheck() ? 200 : listCtrlWidthNoScroll);

			if( m_optionCheck.GetCheck() )
			{
				m_itemListCtrl.InsertColumn(1, "Description", LVCFMT_LEFT, 120);
				m_itemListCtrl.InsertColumn(2, "Timestamp", LVCFMT_LEFT, listCtrlWidthNoScroll - 120 - 200);
			}
			break;
		default:
			ASSERT(FALSE);	// unknown type
			break;
		}
	}
}

/*!
	Set the controls dependent on the Option state
*/
void CItemListDlg::InitOptionCtrls()
{
	BOOL enableDateRev = (kItemListModule == m_type && !m_optionCheck.GetCheck()) ? false : true;
	
	GetDlgItem(IDC_CHECKREV)->EnableWindow(enableDateRev);
	GetDlgItem(IDC_CHECKDATE)->EnableWindow(enableDateRev);
	
	OnCheckrev();
	OnCheckdate();

	switch( m_type )
	{
	case kItemListTag:
		gItemListTagRecurse = m_optionCheck.GetCheck() != 0;
		break;
	case kItemListModule:
		gItemListModuleList = m_optionCheck.GetCheck() != 0;
		break;
	default:
		// Nothing to do
		break;
	}
}

/*!
	Enable or disable controls
	\param enable Set to TRUE to enable controls, FALSE to disable controls
*/
void CItemListDlg::EnableControls(BOOL enable)
{
	GetDlgItem(IDOK)->EnableWindow(enable);
	GetDlgItem(IDC_REFRESH)->EnableWindow(enable);
	m_readFromCombo.EnableWindow(enable);
	m_itemListCtrl.EnableWindow(enable);
	GetDlgItem(IDC_ITEM)->EnableWindow(enable);

	GetDlgItem(IDC_OPTION)->EnableWindow(enable);

	GetDlgItem(IDC_FORCEROOT)->EnableWindow(enable);

	if( !enable )
	{
		GetDlgItem(IDC_CHECKREV)->EnableWindow(FALSE);
		GetDlgItem(IDC_COMBOREV)->EnableWindow(FALSE);
		
		GetDlgItem(IDC_BROWSE_TAG)->EnableWindow(FALSE);
		
		GetDlgItem(IDC_CHECKDATE)->EnableWindow(FALSE);
		GetDlgItem(IDC_COMBODATE)->EnableWindow(FALSE);

		GetDlgItem(IDC_BROWSE_READFROM)->EnableWindow(FALSE);
	}
	else
	{
		InitOptionCtrls();
		OnForceroot();
	}
}

/// BN_CLICKED message handler, re-reads the data
void CItemListDlg::OnRefresh() 
{
	// Verify the settings
	if( !CheckSettings() )
	{
		return;
	}

	// Prepare controls
	EnableControls(FALSE);

	// Change the Cancel button's name to "Stop"
	CString strPrevCancelText;
	GetDlgItem(IDCANCEL)->GetWindowText(strPrevCancelText);
	GetDlgItem(IDCANCEL)->SetWindowText("Stop");

	// Store current item text
	CString strItem;
	GetDlgItem(IDC_ITEM)->GetWindowText(strItem);

	// Display the reading data prompt
	InitListCtrl(TRUE);

	// Select and launch command
	CString strReadFrom;
	CString revision;
	CString date;

	m_readFromCombo.GetWindowText(strReadFrom);

	if( m_hasRevCheck.GetCheck() )
	{
		GetDlgItemText(IDC_COMBOREV, revision);
	}
		
	if( m_hasDateCheck.GetCheck() )
	{
		GetDlgItemText(IDC_COMBODATE, date);
	}

	const char* readFrom = kItemListModule == m_type && 
		m_forceCvsrootCheck.GetCheck() && !m_optionCheck.GetCheck() ? "" : (LPCTSTR)strReadFrom;

	CItemListConsole* itemListConsole = LaunchItemListCommand(m_type, m_optionCheck.GetCheck() != 0, 
		readFrom, m_forceCvsrootCheck.GetCheck() != 0, 
		revision, date, &m_listPath);

	if( !itemListConsole )
	{
		EndDialog(IDCANCEL);
		return;
	}

	// Initialize list control
	InitListCtrl();

	// Display data
	DisplayItems(itemListConsole->GetItemList());

	// Restore control's state
	EnableControls(TRUE);

	// Restore Cancel's button name
	GetDlgItem(IDCANCEL)->SetWindowText(strPrevCancelText);
	
	// Sort items
	SortItems(m_sortColumn, m_sortAsc, m_type, m_optionCheck.GetCheck() != 0);

	// Try to select the item
	if( strItem.IsEmpty() && m_itemListCtrl.GetItemCount() )
	{
		strItem = m_itemListCtrl.GetItemText(0, 0);
	}

	if( !strItem.IsEmpty() )
	{
		SelectItem(strItem);
		GotoDlgCtrl(&m_itemListCtrl);
	}

	// Release any allocated memory
	if( itemListConsole )
	{
		delete itemListConsole;
	}
}

/*!
	Verify that the settings are correct to retrieve items
	\return true if the settings are correct, false otherwise
*/
bool CItemListDlg::CheckSettings()
{
	if( m_hasRevCheck.IsWindowEnabled() && m_hasRevCheck.GetCheck() && !m_revCombo.GetWindowTextLength() )
	{
		AfxMessageBox("Please enter more than 1 character");
		GotoDlgCtrl(&m_revCombo);
		return false;
	}

	if( m_hasDateCheck.IsWindowEnabled() && m_hasDateCheck.GetCheck() && !m_dateCombo.GetWindowTextLength() )
	{
		AfxMessageBox("Please enter more than 1 character");
		GotoDlgCtrl(&m_dateCombo);
		return false;
	}

	return true;
}

/*!
	Display items in the list
	\param itemList List of items to display
	\param showUpDir Set to true to show the up-dir(..) in the list if apropriate
*/
void CItemListDlg::DisplayItems(const ItemList& itemList, bool showUpDir /*= true*/)
{
	ItemList::const_iterator it = itemList.begin();
	while( it != itemList.end() )
	{
		ItemDisplayInfo* displayInfo = new ItemDisplayInfo;
		if( displayInfo )
		{
			displayInfo->Create((*it).first, (*it).second);

			const int itemIndex = m_itemListCtrl.InsertItem(m_itemListCtrl.GetItemCount(), displayInfo->GetItem().c_str());
			if( itemIndex > -1 )
			{
				if( kItemListTag == m_type || kItemListModule == m_type )
				{
					m_itemListCtrl.SetItemText(itemIndex, 1, displayInfo->GetDescription().c_str());
				}
				
				if( kItemListModule == m_type && m_optionCheck.GetCheck() )
				{
					m_itemListCtrl.SetItemText(itemIndex, 2, displayInfo->GetDescriptionEx().c_str());
				}
				
				if( !m_itemListCtrl.SetItemData(itemIndex, (DWORD)displayInfo) )
				{
					delete displayInfo;
				}

				const int image = GetImageForItem(m_type, m_optionCheck.GetCheck() != 0, displayInfo);
				if( image > -1 )
				{
					m_itemListCtrl.SetItem(itemIndex, -1, LVIF_IMAGE, NULL, image, 0, 0, 0);
				}
			}
			else
			{
				delete displayInfo;
			}
		}

		it++;
	}
	
	if( showUpDir && kItemListModule == m_type && m_optionCheck.GetCheck() && !m_listPath.empty() )
	{
		ItemDisplayInfo* displayInfo = new ItemDisplayInfo;
		if( displayInfo )
		{
			ItemRowInfo rowInfo;
			rowInfo.m_description.insert(STR_ITEMLIST_DIR);
				
			displayInfo->Create(string(STR_ITEMLIST_UP_DIR), rowInfo);
				
			const int itemIndex = m_itemListCtrl.InsertItem(0, displayInfo->GetItem().c_str());
			if( itemIndex > -1 )
			{
				m_itemListCtrl.SetItemText(itemIndex, 1, displayInfo->GetDescription().c_str());
				
				if( !m_itemListCtrl.SetItemData(itemIndex, (DWORD)displayInfo) )
				{
					delete displayInfo;
				}
				
				const int image = GetImageForItem(m_type, m_optionCheck.GetCheck() != 0, displayInfo);
				if( image > -1 )
				{
					m_itemListCtrl.SetItem(itemIndex, -1, LVIF_IMAGE, NULL, image, 0, 0, 0);
				}
			}
			else
			{
				delete displayInfo;
			}
		}
	}
}

/*!
	Get the image corresponding to the given item
	\param type Item list type
	\param option Item list option
	\param displayInfo Item info to find the image for
	\return The image index in the image list to be used for item or -1 if not image was selected
*/
int CItemListDlg::GetImageForItem(kItemListDlg type, bool option, const ItemDisplayInfo* displayInfo)
{
	int res = -1;

	switch( type )
	{
	case kItemListTag:
		{
			bool hasRevision = displayInfo->GetDescription().find(STR_ITEMLIST_REVISION) != string::npos;
			bool hasBranch = displayInfo->GetDescription().find(STR_ITEMLIST_BRANCH) != string::npos;
			
			if( hasRevision && hasBranch )
			{
				res = kItemImgBranchAndRevision;
			}
			else if( hasBranch )
			{
				res = kItemImgBranch;
			}
			else
			{
				res = kItemImgRevision;
			}
		}
		break;
	case kItemListModule:
		if( option )
		{
			if( strcmp(displayInfo->GetItem().c_str(), STR_ITEMLIST_UP_DIR) == 0 )
			{
				res = kItemImgUpDir;
			}
			else
			{
				bool isDir = displayInfo->GetDescription().find(STR_ITEMLIST_DIR) != string::npos;
				res = isDir ? kItemImgDir : kItemImgFile;
			}
		}
		else
		{
			res	= kItemImgModule;
		}
		break;
	default:
		ASSERT(FALSE);	// unknown type
		break;
	}

	return res;
}

/*!
	Remove all items and release assotiated memory held in Item Data of the list control
*/
void CItemListDlg::DeleteAllItems()
{
	for(int nIndex = 0; nIndex < m_itemListCtrl.GetItemCount(); nIndex++)
	{
		ItemDisplayInfo* displayInfo = (ItemDisplayInfo*)m_itemListCtrl.GetItemData(nIndex);
		delete displayInfo;
	}

	m_itemListCtrl.DeleteAllItems();
}

/*!
	Find and select the item in the list control
	\param item Item to be found
	\return true if the item was found, false otherwise
	\note Even thought the item state is set to selected and focused it does not set the focus to the list control itself
*/
bool CItemListDlg::SelectItem(const char* item)
{
	bool res = FALSE;

	LVFINDINFO findInfo = { 0 };
	findInfo.flags = LVFI_STRING | LVFI_PARTIAL;
	findInfo.psz = item;

	const int index = m_itemListCtrl.FindItem(&findInfo);
	if( index > -1 )
	{
		m_itemListCtrl.EnsureVisible(index, FALSE);
		m_itemListCtrl.SetItemState(index, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);

		res = true;
	}

	return res;
}

/// OnOK virtual override, store the persistent data
void CItemListDlg::OnOK() 
{
	switch( m_type )
	{
	case kItemListTag:
		gItemListTagRecurse = m_optionCheck.GetCheck() != 0;
		break;
	case kItemListModule:
		gItemListModuleList = m_optionCheck.GetCheck() != 0;
		break;
	default:
		// Nothing to do
		break;
	}
	
	CDialog::OnOK();
}

/// OnCancel virtual override, stop CVS command or cancel dialog
void CItemListDlg::OnCancel() 
{
	CWincvsApp* app = (CWincvsApp*)AfxGetApp();
	if( app && app->IsCvsRunning() && !app->IsCvsStopping() )
	{
		m_itemListCtrl.DeleteAllItems();
		m_itemListCtrl.InsertItem(0, "Stopping...", kItemImgBlank);

		stopCVS();
		return;
	}
	
	CDialog::OnCancel();
}

/// BN_CLICKED message handler, initialize the related controls and sort settings
void CItemListDlg::OnOption() 
{
	InitOptionCtrls();

	if( m_type == kItemListModule )
	{
		m_listPath.clear();

		m_sortAsc = false;
		m_sortColumn = 0;
	}
}

/// BN_CLICKED message handler, enable related controls
void CItemListDlg::OnForceroot() 
{
	bool enableReadFrom = !(kItemListModule == m_type && m_forceCvsrootCheck.GetCheck() && !m_optionCheck.GetCheck());
	m_readFromCombo.EnableWindow(enableReadFrom);

	bool enableBrowseReadFrom = kItemListTag == m_type && m_forceCvsrootCheck.GetCheck();
	GetDlgItem(IDC_BROWSE_READFROM)->EnableWindow(enableBrowseReadFrom);
}

/// BN_CLICKED message handler, enable revision combo box
void CItemListDlg::OnCheckrev() 
{
	bool enable = m_hasRevCheck.GetCheck() && !(kItemListModule == m_type && !m_optionCheck.GetCheck());

	m_revCombo.EnableWindow(enable);

	CWnd* wndBrowse = GetDlgItem(IDC_BROWSE_TAG);
	if( wndBrowse)
	{
		wndBrowse->EnableWindow(enable);
	}
}

/// BN_CLICKED message handler, enable date combo box
void CItemListDlg::OnCheckdate() 
{
	bool enable = m_hasDateCheck.GetCheck() && !(kItemListModule == m_type && !m_optionCheck.GetCheck());
	
	m_dateCombo.EnableWindow(enable);
}

/// BN_CLICKED message handler, browse for tag/branch
void CItemListDlg::OnBrowseTag() 
{
	CPStr tagName;
	if( CompatGetTagListItem(m_mf, tagName) )
	{
		m_rev = (const char*)tagName;
		m_revCombo.SetWindowText(m_rev);
	}
}

/// BN_CLICKED message handler, browse for file/directory
void CItemListDlg::OnBrowseReadfrom() 
{
	CPStr moduleName;
	if( CompatGetModuleListItem(m_mf, moduleName) )
	{
		m_readFromCombo.SetWindowText((const char*)moduleName);
		GotoDlgCtrl(GetDlgItem(IDC_BROWSE_READFROM));
	}
}

/// NM_DBLCLK message handler, navigate directories for directory and files listing
void CItemListDlg::OnDblclkItemlist(NMHDR* pNMHDR, LRESULT* pResult) 
{
	if( kItemListModule == m_type && m_optionCheck.GetCheck() )
	{
		NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
		if( pNMListView->iItem > -1 )
		{
			const CString description = m_itemListCtrl.GetItemText(pNMListView->iItem, 1);
			if( description == STR_ITEMLIST_DIR )
			{
				const CString item = m_itemListCtrl.GetItemText(pNMListView->iItem, 0);
				if( item == STR_ITEMLIST_UP_DIR )
				{
					if( !m_listPath.empty() )
					{
						// Set the item for selection in up-level
						const string backItem = m_listPath.back();
						SetDlgItemText(IDC_ITEM, backItem.c_str());

						m_listPath.pop_back();
					}
				}
				else
				{
					// Make it so it's easy to "return" where we came from
					
					SetDlgItemText(IDC_ITEM, STR_ITEMLIST_UP_DIR);
					m_listPath.push_back((LPCTSTR)item);
				}

				// "Click" the refresh button
				PostMessage(WM_COMMAND, MAKEWPARAM(IDC_REFRESH, BN_CLICKED), (LPARAM)NULL);
			}
		}
	}
	
	*pResult = 0;
}

/// LVN_ITEMCHANGED message handler, update the item edit box value as selection is changing
void CItemListDlg::OnItemchangedItemlist(NMHDR* pNMHDR, LRESULT* pResult) 
{
	NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
	if( pNMListView->iItem > -1 && 
		pNMListView->uChanged & LVIF_STATE && pNMListView->uNewState & (LVIS_SELECTED | LVIS_FOCUSED) )
	{
		CString item;

		if( kItemListModule == m_type && m_optionCheck.GetCheck() )
		{
			item = FormatListPath(m_listPath).c_str();
			
			CString selItem = m_itemListCtrl.GetItemText(pNMListView->iItem, 0);
			if( selItem  != STR_ITEMLIST_UP_DIR )
			{
				item += selItem;
			}
			else
			{
				item.TrimRight('/');
			}
		}
		else
		{
			item = m_itemListCtrl.GetItemText(pNMListView->iItem, 0);
		}

		SetDlgItemText(IDC_ITEM, item);
	}
	
	*pResult = 0;
}

/// LVN_COLUMNCLICK message handler, sort items in response to column click
void CItemListDlg::OnColumnclickItemlist(NMHDR* pNMHDR, LRESULT* pResult) 
{
	NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
	
	if( m_sortColumn != pNMListView->iSubItem )
	{
		m_sortAsc = false;
	}
	else
	{
		m_sortAsc = !m_sortAsc;
	}

	m_sortColumn = pNMListView->iSubItem;

	SortItems(m_sortColumn, m_sortAsc, m_type, m_optionCheck.GetCheck() != 0);

	*pResult = 0;
}

/*!
	Sort items listed in the list control
	\param column Column to sort items by
	\param asc Sort ascending or descending
	\param type List dialog type
	\param option Option state (recurse or list checbox)
	\note Using list control callback CompareItems
*/
void CItemListDlg::SortItems(int column, bool asc, kItemListDlg type, bool option)
{
	m_headerCtrl.SetSortImage(column, asc);

	CompareInfo info = { 0 };
	
	info.m_column = column;
	info.m_asc = asc;
	info.m_type = type;
	info.m_option = option;

	m_itemListCtrl.SortItems(CompareItems, (DWORD)&info);
}

/// WM_DESTROY message handler, release any memory allocated
void CItemListDlg::OnDestroy() 
{
	DeleteAllItems();

	CDialog::OnDestroy();
}

#endif	/* WIN32 */

/// Get Tag item list
bool CompatGetTagListItem(const MultiFiles* mf,
						  CPStr& tagName)
{
	bool userHitOK = false;

	static CPStr sLastTag;
	static ItemList sTagList;
	static bool sLastForceCvsroot = false;
	static CPStr sLastRev;
	static CPStr sLastDate;

#ifdef WIN32
	CItemListDlg itemListDlg(mf, 
		sLastTag, kItemListTag, sTagList, NULL, 
		sLastForceCvsroot, sLastRev, sLastDate);

	if( itemListDlg.DoModal() == IDOK )
	{
		sLastTag = itemListDlg.m_item;
		sLastRev = itemListDlg.m_rev;
		sLastDate = itemListDlg.m_date;
		sLastForceCvsroot = itemListDlg.m_forceCvsroot != 0;

		userHitOK = true;
	}
#endif /* WIN32 */

#if qUnix
#endif // qUnix

#ifdef qMacCvsPP
#endif /* qMacCvsPP */

	if( userHitOK )
	{
		tagName = sLastTag;
	}

	return userHitOK;
}

/// Get Module item list
bool CompatGetModuleListItem(const MultiFiles* mf,
							 CPStr& moduleName)
{
	bool userHitOK = false;

	static CPStr sLastModule;
	static ItemList sLastModuleList;
	static ListPath sLastListPath;
	static bool sLastForceCvsroot = false;
	static CPStr sLastRev;
	static CPStr sLastDate;

#ifdef WIN32
	CItemListDlg itemListDlg(mf, 
		sLastModule, kItemListModule, sLastModuleList, &sLastListPath, 
		sLastForceCvsroot, sLastRev, sLastDate);

	if( itemListDlg.DoModal() == IDOK )
	{
		sLastModule = itemListDlg.m_item;
		sLastListPath = itemListDlg.m_listPath;
		sLastRev = itemListDlg.m_rev;
		sLastDate = itemListDlg.m_date;
		sLastForceCvsroot = itemListDlg.m_forceCvsroot != 0;

		userHitOK = true;
	}
#endif /* WIN32 */

#if qUnix
#endif // qUnix

#ifdef qMacCvsPP
#endif /* qMacCvsPP */

	if( userHitOK )
	{
		moduleName = sLastModule;
	}

	return userHitOK;
}
