/*
** 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 "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
#	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

/*!
	Make a "safe" copy of the string
	\param buffer Destination
	\param value Source
	\param bufferSize Destination buffer size
*/
static void SafeCopy(char* buffer, const char* value, int bufferSize)
{
	if( value && *value )
	{
		strncpy(buffer, value, bufferSize);
	}
	else
	{
		*buffer = 0;
	}
}

/// Name column
const class KoNameColumn : public KiColumn 
{
// Interfaces
public:
#ifdef WIN32
	/// Retrieve column label 
	virtual void GetSetupData(LV_COLUMN* lvc) const
	{
		lvc->mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
		lvc->pszText = _T("Name");
		lvc->cx = 150;
		lvc->fmt = LVCFMT_LEFT;
	}
#else
	/// Retrieve column label 
	virtual void GetSetupData(LV_COLUMN* lvc) const
	{
		lvc->name = _i18n("Name");
		lvc->width = PLATFORM_COLUMN_WIDTH(250, 210);
	}
#endif

	/// Retrieve formatted text as pertained to a node
	virtual void GetText(KoColumnContext* /*context*/, EntnodeData* data, char* buffer, int bufferSize) const
	{
		SafeCopy(buffer, data->GetName(), bufferSize);
	}

	/// Compare two nodes (-1,0,+1)
	virtual int Compare(KoColumnContext* /*context*/, EntnodeData* d1, EntnodeData* d2) const
	{
		int res = stricmp(d1->GetName(), d2->GetName());
		return res;
	}

	/// Tells if the column shall be sorted ascending initially
	virtual bool IsDefaultAscending() const
	{
		return false;
	}
} nameColumn;

/// Path column
const class KoPathColumn : public KiColumn 
{
// Interfaces
public:
	/// Retrieve column label 
#ifdef WIN32
	virtual void GetSetupData(LV_COLUMN* lvc) const
	{
		lvc->mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
		lvc->pszText = _T("Path");
		lvc->cx = 150;
		lvc->fmt = LVCFMT_LEFT;
	}
#else
	/// Retrieve column label 
	virtual void GetSetupData(LV_COLUMN* lvc) const
	{
		lvc->name = _i18n("Path");
		lvc->width = PLATFORM_COLUMN_WIDTH(340, 300);
	}
#endif

	/// Retrieve formatted text as pertained to a node
	virtual void GetText(KoColumnContext* context, EntnodeData* data, char* buffer, int bufferSize) const
	{				
		UStr buf;
		const char* fullpath = data->GetFullPathName(buf);
		int len = context->m_path.length();
		fullpath += len;
		
		if( *fullpath == '\\' )
		{
			fullpath++;
		}

		SafeCopy(buffer, fullpath, bufferSize);

		const char* name = data->GetName();
		if( name )
		{
			int lenName = strlen(name) + 1;
			int lenPath = strlen(buffer);
			if( lenPath <= lenName )
			{
				strncpy(buffer, ".", bufferSize);
			}
			else
			{
				buffer[lenPath - lenName] = 0;
			}
		}
	}

	/// Compare two nodes (-1,0,+1)
	virtual int Compare(KoColumnContext* context, EntnodeData* d1, EntnodeData* d2) const
	{
		char text1[256];
		GetText(context, d1, text1, 256);
		
		char text2[256];
		GetText(context, d2, text2, 256);
		
		return stricmp(text1, text2);
	}

	/// Tells if the column shall be sorted ascending initially
	virtual bool IsDefaultAscending() const
	{
		return false;
	}
} pathColumn;

/// Revision column	
const class KoRevisionColumn : public KiColumn 
{
// Interfaces
public:
	/// Retrieve column label 
#ifdef WIN32
	virtual void GetSetupData(LV_COLUMN* lvc) const
	{
		lvc->mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
		lvc->pszText = _T("Rev.");
		lvc->cx = 50;
		lvc->fmt = LVCFMT_LEFT;
	}
#else
	/// Retrieve column label 
	virtual void GetSetupData(LV_COLUMN* lvc) const
	{
		lvc->name = _i18n("Rev.");
		lvc->width = PLATFORM_COLUMN_WIDTH(82, 70);
	}
#endif

	/// Retrieve formatted text as pertained to a node
	virtual void GetText(KoColumnContext* /*context*/, EntnodeData* data, char* buffer, int bufferSize) const
	{
		SafeCopy(buffer, (*data)[EntnodeFile::kVN], bufferSize);
	}

	/// Compare two nodes (-1,0,+1)
	virtual int Compare(KoColumnContext* /*context*/, EntnodeData* d1, EntnodeData* d2) const
	{
		const char* s1 = (*d1)[EntnodeFile::kVN];
		const char* s2 = (*d2)[EntnodeFile::kVN];
		
		int res;
		if( s1 != 0L && s2 != 0L )
			res = revcmp(s1, s2);
		else
			res = (long)s1 < (long)s2 ? -1 : ((long)s1 > (long)s2 ? 1 : 0);
		
		return res;
	}

	/// Compare two revisions
	static int revcmp(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;
		char* tmp;
		int v1, v2;

		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 revcmp(q1.empty() ? (const char*)0L : q1.c_str(), q2.empty() ? (const char*)0L : q2.c_str());

		return v1 < v2 ? -1 : 1;
	}

	/// Tells if the column shall be sorted ascending initially
	virtual bool IsDefaultAscending() const
	{
		return true;
	}
} revisionColumn;

/// Option column
const class KoOptionColumn : public KiColumn 
{
// Interfaces
public:
	/// Retrieve column label 
#ifdef WIN32
	virtual void GetSetupData(LV_COLUMN* lvc) const
	{
		lvc->mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
		lvc->pszText = _T("Option");
		lvc->cx = 50;
		lvc->fmt = LVCFMT_LEFT;
	}
#else
	/// Retrieve column label 
	virtual void GetSetupData(LV_COLUMN* lvc) const
	{
		lvc->name = _i18n("Option");
		lvc->width = 55;
	}
#endif

	/// Retrieve formatted text as pertained to a node
	virtual void GetText(KoColumnContext* /*context*/, EntnodeData* data, char* buffer, int bufferSize) const
	{
		SafeCopy(buffer, (*data)[EntnodeFile::kOption], bufferSize);
	}

	/// Compare two nodes (-1,0,+1)
	virtual int Compare(KoColumnContext* /*context*/, EntnodeData* d1, EntnodeData* d2) const 
	{
		const char* s1 = (*d1)[EntnodeFile::kOption];
		const char* s2 = (*d2)[EntnodeFile::kOption];
		int res;
		if( s1 != 0L && s2 != 0L )
			res = strcmp(s1, s2);
		else
			res = (long)s1 < (long)s2 ? -1 : ((long)s1 > (long)s2 ? 1 : 0);

		return res;
	}

	/// Tells if the column shall be sorted ascending initially
	virtual bool IsDefaultAscending() const
	{
		return true;
	}
} optionColumn;

/// Status column
const class KoStatusColumn : public KiColumn 
{
// Interfaces
public:
	/// Retrieve column label 
#ifdef WIN32
	virtual void GetSetupData(LV_COLUMN* lvc) const
	{
		lvc->mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
		lvc->pszText = _T("Status");
		lvc->cx = 100;
		lvc->fmt = LVCFMT_LEFT;
	}
#else
	/// Retrieve column label 
	virtual void GetSetupData(LV_COLUMN* lvc) const
	{
		lvc->name = _i18n("Status");
		lvc->width = PLATFORM_COLUMN_WIDTH(76, 70);
	}
#endif

	/// Retrieve formatted text as pertained to a node
	virtual void GetText(KoColumnContext* /*context*/, EntnodeData* data, char* buffer, int bufferSize) const
	{
		SafeCopy(buffer, (*data)[EntnodeData::kStatus], bufferSize);
	}

	/// Compare two nodes (-1,0,+1)
	virtual int Compare(KoColumnContext* /*context*/, EntnodeData* d1, EntnodeData* d2) const
	{
		int res = stricmp((*d1)[EntnodeData::kStatus], (*d2)[EntnodeData::kStatus]);
		return res;
	}

	/// Tells if the column shall be sorted ascending initially
	virtual bool IsDefaultAscending() const
	{
		return true;
	}
} statusColumn;

/// Tag column
const class KoTagColumn : public KiColumn 
{
// Interfaces
public:
	/// Retrieve column label 
#ifdef WIN32
	virtual void GetSetupData(LV_COLUMN* lvc) const
	{
		lvc->mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
		lvc->pszText = _T("Tag");
		lvc->cx = 150;
		lvc->fmt = LVCFMT_LEFT;
	}
#else
	/// Retrieve column label 
	virtual void GetSetupData(LV_COLUMN* lvc) const
	{
		lvc->name = _i18n("Tag");
		lvc->width = 90;
	}
#endif

	/// Retrieve formatted text as pertained to a node
	virtual void GetText(KoColumnContext* /*context*/, EntnodeData* data, char* buffer, int bufferSize) const
	{
		SafeCopy(buffer, (*data)[EntnodeFile::kTag], bufferSize);
	}

	/// Compare two nodes (-1,0,+1)
	virtual int Compare(KoColumnContext* /*context*/, EntnodeData* d1, EntnodeData* d2) const
	{
		const char* s1 = (*d1)[EntnodeFile::kTag];
		const char* s2 = (*d2)[EntnodeFile::kTag];
		int res;
		if( s1 != 0L && s2 != 0L )
			res = strcmp(s1, s2);
		else
			res = (long)s1 < (long)s2 ? -1 : ((long)s1 > (long)s2 ? 1 : 0);

		return res;

	}

	/// Tells if the column shall be sorted ascending initially
	virtual bool IsDefaultAscending() const
	{
		return true;
	}
} tagColumn;

/// Date column
const class KoDateColumn : public KiColumn 
{
// Interfaces
public:
	/// Retrieve column label 
#ifdef WIN32
	virtual void GetSetupData(LV_COLUMN* lvc) const
	{
		lvc->mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
		lvc->pszText = _T("Timestamp");
		lvc->cx = (IsLargeFonts()) ? 170 : 150;
		lvc->fmt = LVCFMT_LEFT;
	}
#else
	/// Retrieve column label 
	virtual void GetSetupData(LV_COLUMN* lvc) const
	{
		lvc->name = _i18n("Timestamp");
		lvc->width = PLATFORM_COLUMN_WIDTH(172, 135);
	}
#endif

	/// Retrieve formatted text as pertained to a node
	virtual void GetText(KoColumnContext* /*context*/, EntnodeData* data, char* buffer, int bufferSize) const
	{
		SafeCopy(buffer, (*data)[EntnodeFile::kTS], bufferSize);
	}

	/// Compare two nodes (-1,0,+1)
	virtual int Compare(KoColumnContext* /*context*/, EntnodeData* d1, EntnodeData* d2) const
	{
		const char* s1 = (*d1)[EntnodeFile::kTS];
		const char* s2 = (*d2)[EntnodeFile::kTS];
		int res = 0;
 
		if( s1 != 0L && s2 != 0L )
		{
			time_t t1 = GetDateTime(s1); 
			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;
	}

	/// Tells if the column shall be sorted ascending initially
	virtual bool IsDefaultAscending() const
	{
		return true;
	}
} dateColumn;

/// Conflict column
const class KoConflictColumn : public KiColumn 
{
// Interfaces
public:
	/// Retrieve column label 
#ifdef WIN32
	virtual void GetSetupData(LV_COLUMN* lvc) const
	{
		lvc->mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
		lvc->pszText = _T("Conflict");
		lvc->cx = 150;
		lvc->fmt = LVCFMT_LEFT;
	}
#else
	/// Retrieve column label 
	virtual void GetSetupData(LV_COLUMN* lvc) const
	{
		lvc->name = _i18n("Conflict");
		lvc->width = 100;
	}
#endif

	/// Retrieve formatted text as pertained to a node
	virtual void GetText(KoColumnContext* /*context*/, EntnodeData* data, char* buffer, int bufferSize) const
	{
		SafeCopy(buffer, data->GetConflict(), bufferSize);
	}

	/// Compare two nodes (-1,0,+1)
	virtual int Compare(KoColumnContext* /*context*/, EntnodeData* d1, EntnodeData* d2) const
	{
		const char* s1 = d1->GetConflict();
		const char* s2 = d2->GetConflict();
		int res;
		if( s1 != 0L && s2 != 0L )
			res = strcmp(s1, s2);
		else
			res = (long)s1 < (long)s2 ? -1 : ((long)s1 > (long)s2 ? 1 : 0);

		return res;
	}

	/// tells if the column shall be sorted ascending initially
	virtual bool IsDefaultAscending() const
	{
		return true;
	}
} conflictColumn;

/// All known columns for view's perusal
static const KiColumn* columnsRegular[] = {
	&nameColumn, &revisionColumn, &optionColumn, &statusColumn, &tagColumn, &dateColumn, &conflictColumn
};

static const int typesRegular[] = {
	EntnodeData::kName, EntnodeFile::kVN, EntnodeFile::kOption, EntnodeData::kStatus,
	EntnodeFile::kTag, EntnodeFile::kTS, EntnodeFile::kConflict
};

static const KiColumn* columnsRecursive[] = {
	&nameColumn, &pathColumn, &revisionColumn, &optionColumn, &statusColumn, &tagColumn, &dateColumn, &conflictColumn
};

static const int typesRecursive[] = {
	EntnodeData::kName, EntnodeFile::kPath, EntnodeFile::kVN, EntnodeFile::kOption, EntnodeData::kStatus,
	EntnodeFile::kTag, EntnodeFile::kTS, EntnodeFile::kConflict
};

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

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

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

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

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

////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Instantiation

KiColumnModel* KiColumnModel::GetRecursiveModel()
{
	return &modelRecursive;
}
	
KiColumnModel* KiColumnModel::GetRegularModel()
{
	return &modelRegular;
}

int KiColumnModel::GetColumn(int type) const
{
	int numColumns = GetCount();
	for(int i = 0; i < numColumns; i++)
	{
		if(type == GetType(i))
			return i;
	}
	
	return 0;
}

#ifdef WIN32
int CALLBACK _Compare(LPARAM data1, LPARAM data2, LPARAM data)
#else
int _Compare(void* data1, void* data2, void* data)
#endif
{
	int res = 0;
	bool fOneDirOnly = false;

	EntnodeData* d1 = (EntnodeData*)data1;
	EntnodeData* d2 = (EntnodeData*)data2;
	
	const CSortParam* pSortParam = (CSortParam*)data;

	//	Group sub dirs ?
	if( pSortParam->m_fGroupSubDir )
	{
		int d1IsDir = d1->GetType()==ENT_SUBDIR;
		int d2IsDir = d2->GetType()==ENT_SUBDIR;

		if( (d1IsDir + d2IsDir) == 1)
		{
			//	One (and only one of them) is a directory
			fOneDirOnly = true;
			res = d1IsDir ? -1 : 1;
		}
	}
	
	if( !res )
	{
		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( !fOneDirOnly )
		res = pSortParam->m_fAccendant ? -res : res;
	
	return res;
}
