/*
** 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.
*/

#include "stdafx.h"

#include "FileTraversal.h"
#include "BrowseViewColumn.h"
#include "CvsEntries.h"
#include "cvsgui_i18n.h"

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


#if TIME_WITH_SYS_TIME
#	include <sys/time.h>
#	include <time.h>
#elif HAVE_SYS_TIME_H
#	include <sys/time.h>
#else
#	include <time.h>
#endif

#if qMacPP
#	include "MacMisc.h"
#endif

#ifndef _countof
	/// Get the size of an array
#	define _countof(array) (sizeof(array)/sizeof(array[0]))
#endif

#if qMacPP
# define PLATFORM_COLUMN_WIDTH(mac, other) mac
#else
# define PLATFORM_COLUMN_WIDTH(mac, other) other
#endif

#ifndef MAX_PATH
#define MAX_PATH 2048
#endif

/*!
	Compare callback function
	\param data1 First node data
	\param data2 Second node data
	\param data Sort parameters data
	\return -1, 0 or +1
*/
#ifdef WIN32
int CALLBACK _Compare(LPARAM data1, LPARAM data2, LPARAM data)
#else
int _Compare(void* data1, void* data2, void* data)
#endif
{
	int res = 0;
	
	const EntnodeData* d1 = (const EntnodeData*)data1;
	const EntnodeData* d2 = (const EntnodeData*)data2;
	
	const CSortParam* pSortParam = (CSortParam*)data;
	
	bool overrideCompare = false;
	
	if( pSortParam->m_fGroupSubDir )
	{
		// Group sub-directories
		const int d1IsDir = d1->GetType() == ENT_SUBDIR ? 1 : 0;
		const int d2IsDir = d2->GetType() == ENT_SUBDIR ? 1 : 0;
		
		if( (d1IsDir + d2IsDir) == 1 )
		{
			// One (and only one of them) is a directory
			overrideCompare = true;
			res = d1IsDir ? -1 : 1;
		}
	}
	
	if( !overrideCompare )
	{
		const int columnIndex = pSortParam->m_column->GetColumnIndex();
		
		if( columnIndex == ColumnIndex::kOption || 
			columnIndex == ColumnIndex::kState || 
			columnIndex == ColumnIndex::kStickyTag || 
			columnIndex == ColumnIndex::kConflict )
		{
			// Make the non-empty items show on top
			const char* s1;
			const char* s2;
			const int f1IsEmpty = ((s1 = (*d1)[pSortParam->m_column->GetColumnIndex()]) == NULL || strcmp(s1, "") == 0) ? 1 : 0;
			const int f2IsEmpty = ((s2 = (*d2)[pSortParam->m_column->GetColumnIndex()]) == NULL || strcmp(s2, "") == 0) ? 1 : 0;
			
			if( (f1IsEmpty + f2IsEmpty) == 1 )
			{
				// One (and only one of them) is empty
				overrideCompare = true;
				res = f1IsEmpty ? 1 : -1;
			}
		}
	}
	
	if( !overrideCompare )
	{
		res = pSortParam->m_column->Compare(pSortParam->m_context, d1, d2);
		
		if( res == 0 && pSortParam->m_column != pSortParam->m_columnAlt )
		{
			res = pSortParam->m_columnAlt->Compare(pSortParam->m_context, d1, d2);
		}
		else if( pSortParam->m_fAccendant )
		{
			res = -res;
		}
	}

	return res;
}

//////////////////////////////////////////////////////////////////////////
// KoColumnContext

KoColumnContext::KoColumnContext(const char* path)
	: m_path(path)
{
}	

/*!
	Get the current path
	\return The current path
*/
const UStr& KoColumnContext::GetPath() const
{
	return m_path;
}

//////////////////////////////////////////////////////////////////////////
// KoNameColumn

/// Name column
const class KoNameColumn : public KiColumn 
{
public:
	// Interface
	virtual int GetColumnIndex() const
	{
		return ColumnIndex::kName;
	}
	
	virtual void GetSetupData(LV_COLUMN* lvc) const
	{
#ifdef WIN32
		lvc->mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
		lvc->pszText = (char*)ColumnIndex::GetColumnName(GetColumnIndex());
		lvc->cx = 150;
		lvc->fmt = LVCFMT_LEFT;
#else
		lvc->name = ColumnIndex::GetColumnName(GetColumnIndex());
		lvc->width = PLATFORM_COLUMN_WIDTH(250, 210);
#endif
	}

	virtual void GetText(const KoColumnContext* context, const EntnodeData* data, char* buffer, const int bufferSize) const
	{
		StrSafeCopy(buffer, data->GetName(), bufferSize);
	}

	virtual int Compare(const KoColumnContext* context, const EntnodeData* d1, const EntnodeData* d2) const
	{
		return StrOrdICmp(d1->GetName(), d2->GetName());
	}

	virtual bool IsDefaultAscending() const
	{
		return false;
	}
} nameColumn;

//////////////////////////////////////////////////////////////////////////
// KoExtColumn

/// Extension column
const class KoExtColumn : public KiColumn 
{
public:
	// Interface
	virtual int GetColumnIndex() const
	{
		return ColumnIndex::kExtention;
	}
	
	virtual void GetSetupData(LV_COLUMN* lvc) const
	{
#ifdef WIN32
		lvc->mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
		lvc->pszText = (char*)ColumnIndex::GetColumnName(GetColumnIndex());
		lvc->cx = 50;
		lvc->fmt = LVCFMT_LEFT;
#else
		lvc->name = ColumnIndex::GetColumnName(GetColumnIndex());
		lvc->width = PLATFORM_COLUMN_WIDTH(150, 60);
#endif
	}
	
	virtual void GetText(const KoColumnContext* context, const EntnodeData* data, char* buffer, const int bufferSize) const
	{
		StrSafeCopy(buffer, data->GetExtension(), bufferSize);
	}

	virtual int Compare(const KoColumnContext* context, const EntnodeData* d1, const EntnodeData* d2) const
	{
		int res = 0;

		// Compare extensions
		const char* p1 = d1->GetExtension();
		const char* p2 = d2->GetExtension();
		
		if( p1 && p2 ) 
		{
			res = stricmp(p1, p2);
			if( res == 0 ) 
			{
				// Compare filenames, if ext. are identical
				res = stricmp(d1->GetName(), d2->GetName());
			}
		}
		else if( p1 )
		{
			res = +1;
		}
		else if( p2 )
		{
			res = -1;
		}
		else
		{
			// Compare filenames, if their are no ext.
			res = stricmp(d1->GetName(), d2->GetName());
		}
		
		return res;
	}

	virtual bool IsDefaultAscending() const
	{
		return false;
	}
} extColumn;

//////////////////////////////////////////////////////////////////////////
// KoPathColumn

/// Path column
const class KoPathColumn : public KiColumn 
{
public:
	// Interface
	virtual int GetColumnIndex() const
	{
		return ColumnIndex::kPath;
	}
	
	virtual void GetSetupData(LV_COLUMN* lvc) const
	{
#ifdef WIN32
		lvc->mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
		lvc->pszText = (char*)ColumnIndex::GetColumnName(GetColumnIndex());
		lvc->cx = 150;
		lvc->fmt = LVCFMT_LEFT;
#else
		lvc->name = ColumnIndex::GetColumnName(GetColumnIndex());
		lvc->width = PLATFORM_COLUMN_WIDTH(340, 300);
#endif
	}

	virtual void GetText(const KoColumnContext* context, const EntnodeData* data, char* buffer, const int bufferSize) const
	{				
		const EntnodeFile* node = dynamic_cast<const EntnodeFile*>(data);
		StrSafeCopy(buffer, node ? node->GetRelativePath(context) : "", bufferSize);
	}

	virtual int Compare(const KoColumnContext* context, const EntnodeData* d1, const EntnodeData* d2) const
	{
		char text1[MAX_PATH];
		GetText(context, d1, text1, MAX_PATH);
		
		char text2[MAX_PATH];
		GetText(context, d2, text2, MAX_PATH);
		
		return StrOrdICmp(text1, text2);
	}

	virtual bool IsDefaultAscending() const
	{
		return false;
	}
} pathColumn;

//////////////////////////////////////////////////////////////////////////
// KoRevisionColumn

/// Revision column	
const class KoRevisionColumn : public KiColumn 
{
private:
	// Methods

	/*!
		Compare two revisions
		\param rev1 First revision
		\param rev2 Second revision
		\return -1, 0 or 1
	*/
	static int CompareRevisions(const char* rev1, const char* rev2)
	{
		if( rev1 == 0L && rev2 == 0L )
		{
			return 0;
		}
		else if( rev1 == 0L || rev2 == 0L )
		{
			return rev1 == 0L ? -1 : 1;
		}

		CStr r1(rev1), r2(rev2);
		CStr q1, q2;
		int v1, v2;
		char* tmp;

		if( (tmp = strchr(r1, '.')) != 0L )
		{
			tmp[0] = '\0';
			q1 = tmp + 1;
		}

		v1 = atoi(r1);

		if( (tmp = strchr(r2, '.')) != 0L )
		{
			tmp[0] = '\0';
			q2 = tmp + 1;
		}

		v2 = atoi(r2);

		if( v1 == v2 )
		{
			return CompareRevisions(q1.empty() ? (const char*)0L : q1.c_str(), q2.empty() ? (const char*)0L : q2.c_str());
		}

		return v1 < v2 ? -1 : 1;
	}

public:
	// Interface
	virtual int GetColumnIndex() const
	{
		return ColumnIndex::kRevision;
	}
	
	virtual void GetSetupData(LV_COLUMN* lvc) const
	{
#ifdef WIN32
		lvc->mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
		lvc->pszText = (char*)ColumnIndex::GetColumnName(GetColumnIndex());
		lvc->cx = 50;
		lvc->fmt = LVCFMT_LEFT;
#else
		lvc->name = ColumnIndex::GetColumnName(GetColumnIndex());
		lvc->width = PLATFORM_COLUMN_WIDTH(82, 70);
#endif
	}

	virtual void GetText(const KoColumnContext* context, const EntnodeData* data, char* buffer, const int bufferSize) const
	{
		StrSafeCopy(buffer, (*data)[GetColumnIndex()], bufferSize);
	}

	virtual int Compare(const KoColumnContext* context, const EntnodeData* d1, const EntnodeData* d2) const
	{
		int res;

		const char* s1 = (*d1)[GetColumnIndex()];
		const char* s2 = (*d2)[GetColumnIndex()];
		
		if( s1 != 0L && s2 != 0L )
		{
			res = CompareRevisions(s1, s2);
		}
		else
		{
			res = (long)s1 < (long)s2 ? -1 : ((long)s1 > (long)s2 ? 1 : 0);
		}
		
		return res;
	}
} revisionColumn;

//////////////////////////////////////////////////////////////////////////
// KoOptionColumn

/// Option column
const class KoOptionColumn : public KiColumn 
{
public:
	// Interface
	virtual int GetColumnIndex() const
	{
		return ColumnIndex::kOption;
	}
	
	virtual void GetSetupData(LV_COLUMN* lvc) const
	{
#ifdef WIN32
		lvc->mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
		lvc->pszText = (char*)ColumnIndex::GetColumnName(GetColumnIndex());
		lvc->cx = 50;
		lvc->fmt = LVCFMT_LEFT;
#else
		lvc->name = ColumnIndex::GetColumnName(GetColumnIndex());
		lvc->width = 55;
#endif
	}

	virtual void GetText(const KoColumnContext* context, const EntnodeData* data, char* buffer, const int bufferSize) const
	{
		StrSafeCopy(buffer, (*data)[GetColumnIndex()], bufferSize);
	}

	virtual int Compare(const KoColumnContext* context, const EntnodeData* d1, const EntnodeData* d2) const 
	{
		int res;

		const char* s1 = (*d1)[GetColumnIndex()];
		const char* s2 = (*d2)[GetColumnIndex()];
		
		if( s1 != 0L && s2 != 0L )
		{
			res = strcmp(s1, s2);
		}
		else
		{
			res = (long)s1 < (long)s2 ? -1 : ((long)s1 > (long)s2 ? 1 : 0);
		}

		return res;
	}
} optionColumn;

//////////////////////////////////////////////////////////////////////////
// KoEncodingColumn

/// Encoding column
const class KoEncodingColumn : public KiColumn 
{
public:
	// Interface
	virtual int GetColumnIndex() const
	{
		return ColumnIndex::kEncoding;
	}
	
	virtual void GetSetupData(LV_COLUMN* lvc) const
	{
#ifdef WIN32
		lvc->mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
		lvc->pszText = (char*)ColumnIndex::GetColumnName(GetColumnIndex());
		lvc->cx = 60;
		lvc->fmt = LVCFMT_LEFT;
#else
		lvc->name = ColumnIndex::GetColumnName(GetColumnIndex());
		lvc->width = 60;
#endif
	}
	
	virtual void GetText(const KoColumnContext* context, const EntnodeData* data, char* buffer, const int bufferSize) const
	{
		StrSafeCopy(buffer, (*data)[GetColumnIndex()], bufferSize);
	}
	
	virtual int Compare(const KoColumnContext* context, const EntnodeData* d1, const EntnodeData* d2) const
	{
		int res;
		
		const char* s1 = (*d1)[GetColumnIndex()];
		const char* s2 = (*d2)[GetColumnIndex()];
		
		if( s1 != 0L && s2 != 0L )
		{
			res = stricmp(s1, s2);
		}
		else
		{
			res = (long)s1 < (long)s2 ? -1 : ((long)s1 > (long)s2 ? 1 : 0);
		}
		
		return res;
	}
} encodingColumn;

//////////////////////////////////////////////////////////////////////////
// KoStateColumn

/// State column
const class KoStateColumn : public KiColumn 
{
public:
	// Interface
	virtual int GetColumnIndex() const
	{
		return ColumnIndex::kState;
	}
	
	virtual void GetSetupData(LV_COLUMN* lvc) const
	{
#ifdef WIN32
		lvc->mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
		lvc->pszText = (char*)ColumnIndex::GetColumnName(GetColumnIndex());
		lvc->cx = 70;
		lvc->fmt = LVCFMT_LEFT;
#else
		lvc->name = ColumnIndex::GetColumnName(GetColumnIndex());
		lvc->width = PLATFORM_COLUMN_WIDTH(76, 70);
#endif
	}

	virtual void GetText(const KoColumnContext* context, const EntnodeData* data, char* buffer, const int bufferSize) const
	{
		StrSafeCopy(buffer, (*data)[GetColumnIndex()], bufferSize);
	}

	virtual int Compare(const KoColumnContext* context, const EntnodeData* d1, const EntnodeData* d2) const
	{
		int res;
		
		const char* s1 = (*d1)[GetColumnIndex()];
		const char* s2 = (*d2)[GetColumnIndex()];
		
		if( s1 != 0L && s2 != 0L )
		{
			res = stricmp(s1, s2);
		}
		else
		{
			res = (long)s1 < (long)s2 ? -1 : ((long)s1 > (long)s2 ? 1 : 0);
		}
		
		return res;
	}
} stateColumn;

//////////////////////////////////////////////////////////////////////////
// KoTagColumn

/// Tag column
const class KoTagColumn : public KiColumn 
{
public:
	// Interface
	virtual int GetColumnIndex() const
	{
		return ColumnIndex::kStickyTag;
	}
	
	virtual void GetSetupData(LV_COLUMN* lvc) const
	{
#ifdef WIN32
		lvc->mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
		lvc->pszText = (char*)ColumnIndex::GetColumnName(GetColumnIndex());
		lvc->cx = 150;
		lvc->fmt = LVCFMT_LEFT;
#else
		lvc->name = ColumnIndex::GetColumnName(GetColumnIndex());
		lvc->width = 90;
#endif
	}

	virtual void GetText(const KoColumnContext* context, const EntnodeData* data, char* buffer, const int bufferSize) const
	{
		StrSafeCopy(buffer, (*data)[GetColumnIndex()], bufferSize);
	}

	virtual int Compare(const KoColumnContext* context, const EntnodeData* d1, const EntnodeData* d2) const
	{
		int res = 0;

		const char* s1 = (*d1)[GetColumnIndex()];
		const char* s2 = (*d2)[GetColumnIndex()];

		if( s1 != 0L && s2 != 0L )
		{
			res = strcmp(s1, s2);
		}
		else
		{
			res = (long)s1 < (long)s2 ? -1 : ((long)s1 > (long)s2 ? 1 : 0);
		}

		return res;

	}
} tagColumn;

//////////////////////////////////////////////////////////////////////////
// KoDateColumn

/// Date column
const class KoDateColumn : public KiColumn 
{
public:
	// Interface
	virtual int GetColumnIndex() const
	{
		return ColumnIndex::kTimeStamp;
	}
	
	virtual void GetSetupData(LV_COLUMN* lvc) const
	{
#ifdef WIN32
		lvc->mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
		lvc->pszText = (char*)ColumnIndex::GetColumnName(GetColumnIndex());
		lvc->cx = (IsLargeFonts()) ? 170 : 150;
		lvc->fmt = LVCFMT_LEFT;
#else
		lvc->name = ColumnIndex::GetColumnName(GetColumnIndex());
		lvc->width = PLATFORM_COLUMN_WIDTH(172, 135);
#endif
	}

	virtual void GetText(const KoColumnContext* context, const EntnodeData* data, char* buffer, const int bufferSize) const
	{
		StrSafeCopy(buffer, (*data)[GetColumnIndex()], bufferSize);
	}

	virtual int Compare(const KoColumnContext* context, const EntnodeData* d1, const EntnodeData* d2) const
	{
		int res = 0;
		
		const char* s1 = (*d1)[GetColumnIndex()];
		const char* s2 = (*d2)[GetColumnIndex()];
		
		if( s1 != 0L && s2 != 0L )
		{
			const time_t t1 = GetDateTime(s1);
			const time_t t2 = GetDateTime(s2);

			res = t1 < t2 ? -1 : (t1 > t2 ? 1 : 0);
		} 
		else
		{
			res = (long)s1 < (long)s2 ? -1 : ((long)s1 > (long)s2 ? 1 : 0);
		}

		return res;
	}
} dateColumn;

//////////////////////////////////////////////////////////////////////////
// KoConflictColumn

/// Conflict column
const class KoConflictColumn : public KiColumn 
{
public:
	// Interface
	virtual int GetColumnIndex() const
	{
		return ColumnIndex::kConflict;
	}
	
	virtual void GetSetupData(LV_COLUMN* lvc) const
	{
#ifdef WIN32
		lvc->mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
		lvc->pszText = (char*)ColumnIndex::GetColumnName(GetColumnIndex());
		lvc->cx = 150;
		lvc->fmt = LVCFMT_LEFT;
#else
		lvc->name = ColumnIndex::GetColumnName(GetColumnIndex());
		lvc->width = 100;
#endif
	}

	virtual void GetText(const KoColumnContext* context, const EntnodeData* data, char* buffer, const int bufferSize) const
	{
		StrSafeCopy(buffer, data->GetConflict(), bufferSize);
	}

	virtual int Compare(const KoColumnContext* context, const EntnodeData* d1, const EntnodeData* d2) const
	{
		int res;

		const char* s1 = d1->GetConflict();
		const char* s2 = d2->GetConflict();

		if( s1 != 0L && s2 != 0L )
		{
			res = strcmp(s1, s2);
		}
		else
		{
			res = (long)s1 < (long)s2 ? -1 : ((long)s1 > (long)s2 ? 1 : 0);
		}

		return res;
	}
} conflictColumn;

//////////////////////////////////////////////////////////////////////////
// Columns

/// All columns for regular view
static const KiColumn* columnsRegular[] = {
	&nameColumn, &extColumn, 
	&revisionColumn, &optionColumn, &encodingColumn, &stateColumn, 
	&tagColumn, &dateColumn, &conflictColumn
};

/// All columns types for regular view
static const int typesRegular[] = {
	EntnodeData::kName, EntnodeFile::kExt, 
	EntnodeFile::kVN, EntnodeFile::kOption, EntnodeFile::kEncoding, EntnodeData::kState,
	EntnodeFile::kTag, EntnodeFile::kTS, EntnodeFile::kConflict
};

/// All columns for flat view
static const KiColumn* columnsRecursive[] = {
	&nameColumn, &extColumn, &pathColumn, 
	&revisionColumn, &optionColumn, &encodingColumn, &stateColumn, 
	&tagColumn, &dateColumn, &conflictColumn
};

/// All columns types for flat view
static const int typesRecursive[] = {
	EntnodeData::kName, EntnodeFile::kExt, EntnodeFile::kPath, 
	EntnodeFile::kVN, EntnodeFile::kOption, EntnodeFile::kEncoding, EntnodeData::kState,
	EntnodeFile::kTag, EntnodeFile::kTS, EntnodeFile::kConflict
};

//////////////////////////////////////////////////////////////////////////
// KoRegularModel

/// Regular columnar model
class KoRegularModel : public KiColumnModel
{
public:
	// Interface
	virtual const KiColumn* GetAt(const int pos) const
	{
		return columnsRegular[pos];
	}
	
	virtual int GetType(const int pos) const
	{
		return typesRegular[pos];
	}

	virtual int GetCount() const
	{
		return _countof(columnsRegular);
	}
} modelRegular;

//////////////////////////////////////////////////////////////////////////
// KoRecursiveModel

/// Recursive columnar model
class KoRecursiveModel : public KiColumnModel
{	
public:
	// Interface
	virtual const KiColumn* GetAt(const int pos) const
	{
		return columnsRecursive[pos];
	}

	virtual int GetType(const int pos) const
	{
		return typesRecursive[pos];
	}

	virtual int GetCount() const
	{
		return _countof(columnsRecursive);
	}
} modelRecursive;

//////////////////////////////////////////////////////////////////////////
// KiColumnModel

/*!
	Get the recursive model
	\return The recursive model
*/
KiColumnModel* KiColumnModel::GetRecursiveModel()
{
	return &modelRecursive;
}
	
/*!
	Get the regular model
	\return The regular model
*/
KiColumnModel* KiColumnModel::GetRegularModel()
{
	return &modelRegular;
}

/*!
	Get the column index by its type
	\param type Column type
	\return Column index
*/
int KiColumnModel::GetColumn(const int type) const
{
	const int numColumns = GetCount();

	for(int i = 0; i < numColumns; i++)
	{
		if( type == GetType(i) )
		{
			return i;
		}
	}
	
	return 0;
}
