/*
** 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> --- January 1997
 */

#include "stdafx.h"

#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <new>
#include <string>
#if defined (WIN32)
#include <io.h>
#endif
#if defined (TARGET_OS_MAC)
#include "MacMisc.h"
#endif

#include "cvsgui_i18n.h"

#include "ustr.h"
#include "uconsole.h"
#include "FileTraversal.h"
#include "AppConsole.h"

#if qMacAPP
#	include <Errors.h>
#endif

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

#if TARGET_RT_MAC_MACHO || qUnix
#	ifndef HAVE_STRICMP
#		define stricmp strcasecmp
#	endif
#	ifndef HAVE_STRNICMP
#		define strnicmp strncasecmp
#	endif
#endif
	
static const unsigned int kMaxChar = 255;

/*!
	Clean up the log message for the server and put the good line feeds
	\param msg Message to be cleaned
	\return The cleaned message
	\todo mac : ISO conversion
*/
char* CleanupLogMsg(const char* msg)
{
	static CStaticAllocT<char> buf;

	size_t len = strlen(msg);
	buf.AdjustSize(len + 1);

	char* tmp = buf;
	char c;


	while( (c = *msg++) != '\0' )
	{
#ifdef WIN32
		if(c == '\r')
			continue;
#endif /* WIN32 */
#ifdef TARGET_OS_MAC
		if(c == '\r')
			c = '\n';
#endif /* TARGET_OS_MAC */
		*tmp++ = c;
	}

	*tmp++ = '\0';

	return buf;
}

/*!
	Split the path into the up-directory and the folder
	\param dir Path to be split
	\param uppath Return up-directory
	\param folder Return folder or file
	\param dontSkipDelimiter Set to true to prevent skipping the delimiter if the path to be split ends with one
	\return true on success, false otherwise
	\note Passing NULL is considered an error, passing empty string returns true
*/
bool SplitPath(const char* dir, UStr& uppath, UStr& folder, bool dontSkipDelimiter /*= false*/)
{
	char newDir[MAX_PATH * 2] = "";
	char oldDir[MAX_PATH * 2] = "";
	char newFile[MAX_PATH] = "";

	if( !dir )
	{
		// Null pointer
		return false;
	}

	int dirLen = strlen(dir);
	if( dirLen > 0 )
	{
		strcpy(oldDir, dir);
		
		if( !dontSkipDelimiter && (dir[dirLen - 1] == kPathDelimiter) )
		{
			oldDir[dirLen - 1] = '\0';
			--dirLen;
		}
		
		const char* tmp = oldDir;
		const char* prev = oldDir;
		
		while( (tmp = strchr(tmp, kPathDelimiter)) != 0L )
		{
			prev = ++tmp;
		}
		
		strcpy(newDir, oldDir);
		newDir[prev - oldDir] = '\0';
		strcpy(newFile, prev);
	}
		
	uppath = newDir;
	folder = newFile;

	return true;
}

/*!
	Find a unique name for a temporary file
	\param file Return temporary file name
	\param prefix Prefix of the filename
	\param extension Extension of the filename
	\param create Set to true to create the file
	\return true on success, false otherwise
*/
bool MakeTmpFile(UStr& file, const char* prefix, const char* extension /*= 0L*/, bool create /*= false*/)
{
	static int count = 0;

	UStr tmpPath;
	UStr ext;
	UStr prf;

	if( extension != 0L && extension[0] != '\0' )
	{
		if( extension[0] != '.' )
			ext = ".";

		ext << extension;
	}

	if( prefix != 0L && prefix[0] != '\0' )
	{
		prf = prefix;
	}
	else
	{
		prf = "cvsgui";
	}

#ifdef WIN32
	char* ptr;
	ptr = getenv(_T("TMP"));
	if( ptr != 0L && _access(ptr, 0) != -1 )
	{
		tmpPath = ptr;
	}
	else
	{
		tmpPath = ".";
	}
#endif /* !WIN32 */
#ifdef TARGET_OS_MAC
	UFSSpec theFolder;
	OSErr err;
	
	if( (err = MacGetTempFolder(theFolder, tmpPath)) != noErr )
	{
		cvs_err(_i18n("Unable to locate the preferences folder (error %d) !\n"), err);
		return 0L;
	}
#endif /* !TARGET_OS_MAC */
#if qUnix
	tmpPath = "/tmp";
#endif

	struct stat sb;
	if( stat(tmpPath, &sb) == -1 || !S_ISDIR(sb.st_mode) )
	{
		cvs_err(_i18n("Unable to access '%s' (error %d) !\n"), (const char*)tmpPath, errno);
		return false;
	}

	if( !tmpPath.endsWith(kPathDelimiter) )
		tmpPath << kPathDelimiter;

	int maxtry = 10000;
	while( true )
	{
		UStr name;

		{
			char filename[MAX_PATH];
			
			sprintf(filename, "%s%d%s", (const char*)prf, count++, (const char*)ext);
			name = filename;
		}
		
		NormalizeFileName(name);
		file = tmpPath;
		file << name;

		if( stat(file, &sb) == -1 && errno == ENOENT )
		{
			if( create )
			{
				FILE* stream = fopen(file, "w");
				if( stream )
				{
					fclose(stream);
				}
			}
		
			return true;
		}

		if( maxtry-- == 0 )
		{
			cvs_err(_i18n("Time out to access '%s' !\n"), (const char*)tmpPath);
			break;
		}
	}

	return false;
}

/*!
	Extract the extension from the filename
	\param file File name to extract extension from
	\param base Return the base of the filename
	\param ext Return the file's extension
*/
void GetExtension(const char* file, UStr& base, UStr& ext)
{
	const char* tmp = strrchr(file, '.');
	
	if( tmp == 0L )
	{
		ext = "";
		base = file;
	}
	else
	{
		ext = tmp + 1;
//		UStr(file).substr( 0, tmp - file, base); // strange ??
		base = UStr(file).substr( 0, tmp - file); // strange ??
	}
}

/*!
	Parse a command like 'user=whohost=cvs'
	\param cmd Command
	\param key Key
	\param value Return value
	\return	true on success, false otherwise
*/
bool GetEnvValue(const char* cmd, const char* key, UStr& value)
{
	const char* tmp = strstr(cmd, key);
	if( tmp == 0L )
		return false;

	tmp = strchr(tmp, '=');
	if( tmp == 0L )
		return false;

	tmp++;

	const char* tmp2 = strchr(tmp, '&');
	if( tmp2 == 0L )
		value = tmp;
	else
		value.set(tmp, tmp2 - tmp);

	return true;
}

/*!
	Convert a command line (possibly quoted) into regular argc, argv arguments
	\param cmdLine Command line
	\param argv Return argv arguments
	\return	The count of arguments (argc)
	\todo Rewrite to eliminate internal static buffer and limitations of command length and arguments count
*/
int StringToArgv(const char* cmdLine, char** argv)
{
	int argc = 0;
	
	char Quote = '\0';					/*	Contains the quote character if we are inside "" or '', or \0 otherwise */ 
	const char*	walker1 = cmdLine;		/*	walker1 traverses the original command line */
	char* walker2;						/*	walker2 traverses the parsed copy of the commandline */
	char theChar;						/*	theChar is the character we are currently parsing */
	static char arguments[MAX_CMD_LEN];	/*	Will contain a parsed copy of the command line */
	
	/* Copy into arguments */
	walker2 = arguments;
	
	/* Parse command line to \0 */										
	while( (theChar = *walker1++) != '\0' ) 
	{
		/* Skip leading space before an argument */					
		if( theChar == ' ' )
			continue;
		
		/* Set argv to point to the space where we copy the next argument */
		argv [argc++] = walker2;								
		
		/* If we filled argv, skip the rest */															
		if( argc > MAX_ARGS)
		{
			argc--;
			break;
		}

		do
		{
			/* Parse an argument skip */
			/* When we find a quote... */
			if( theChar == '"' || theChar == '\'' )  
			{
				/* If we are not inside quotes already, */
				if( Quote == '\0' )
				{
					/* Remember we are inside quotes now */						
					Quote = theChar;
					
					/* Go to the next character */						
					continue;
				}
				
				/* If we are already inside that kind of quotes */
				if( theChar == Quote )
				{
					/* Close quotes */
					Quote = '\0';							
					
					/* Go to the next character */						
					continue;
				}
			}
			
			/* Copy the character into args */
			*walker2++ = theChar;								
																
			/* Repeat until we reach the end of arguments or we reach a space that is not inside quotes */															
		}while( (*walker1 != '\0') && (((theChar = *walker1++) != ' ') || (Quote != '\0')) );
		
		/* Null-terminate the last argument */
		*walker2++ = 0;											
	}

	return argc;
}

/*!
	Replace all instance of string with another string
	\param strRes String to replace strings in
	\param strFind String to be replaced
	\param strReplace Replacement string
	\return Returns reference to strRes
*/
std::string& Replace(std::string& strRes, const char* strFind, const char* strReplace)
{
	if( !strRes.empty() && strFind && strReplace )
	{
		const int findLen = strlen(strFind);
		const int replaceLen = strlen(strReplace);
		
		std::string::size_type pos = 0;
		while( (pos = strRes.find(strFind, pos)) != std::string::npos )
		{
			strRes.replace(pos, findLen, strReplace);
			pos += replaceLen;
		}
	}
	
	return strRes;
}

#if defined(WIN32) || defined(qUnix)
/*!
	Removes folder separators by replacing them with dots so that temporary file 
	could be created without creating matching subfolders in the temporary file folder
	\param tmpf File with subfolder
	\return true on success, false otherwise
	\note Windows client may provide file names pointing into subfolders
*/
void FlatenSubFolders(UStr& tmpf)
{
    tmpf.replace( kPathDelimiter, '.');
}

#elif TARGET_OS_MAC
void FlatenSubFolders(UStr& tmpf)
{
	char *tmp;
	if((tmp = strrchr(tmpf, kPathDelimiter)) != NULL)
	{
		UStr newStr(++tmp);
		tmpf = newStr;
	}
}
#else
#define FlatenSubFolders(tmpf) // no-op on other platforms
#endif

/*!
	Normalize given file name
	\param fileName Return normalized file name
	\return Normalized file name reference
*/
UStr& NormalizeFileName(UStr& fileName)
{
	fileName.replace('*', '_');
	fileName.replace('?', '_');
	fileName.replace(':', '_');
	fileName.replace('/', '_');
	fileName.replace('\\', '_');
	fileName.replace('"', '_');
	fileName.replace('$', '_');

	return fileName;
}

/*!
	Make a "safe" copy of the string
	\param buffer Destination
	\param value Source
	\param bufferSize Destination buffer size
*/
void StrSafeCopy(char* buffer, const char* value, const int bufferSize)
{
	if( value && *value )
	{
		strncpy(buffer, value, bufferSize);
	}
	else
	{
		*buffer = 0;
	}
}
	
/*!
	Compare strings without regard to case but assuring the order if only case differs
	\param s1 First string
	\param s2 Second string
	\return -1, 0 and 1 as appropriate
*/
int StrOrdICmp(const char* s1, const char* s2)
{
	if( int rc = stricmp(s1, s2) )
		return rc;
	
	return strcmp(s1, s2);
}

/*!
	Compare paths ignoring trailing path delimiter
	\param s1 First path
	\param s2 Second path
	\return -1, 0 and 1 as appropriate
*/
int StrPathCmp(const char* s1, const char* s2)
{
	CStr str1(s1);
	CStr str2(s2);

	str1.rtrim(kPathDelimiter);
	str2.rtrim(kPathDelimiter);

#ifdef WIN32
	return stricmp(str1.c_str(), str2.c_str());
#else
	return strcmp(str1.c_str(), str2.c_str());
#endif
}

//////////////////////////////////////////////////////////////////////////
// UPStr

UPStr::~UPStr()
{
	clear();
}

/*!
	Clear the string
*/
void UPStr::clear(void)
{
	if( m_str )
	{
		free(m_str);
		m_str = 0L;
	}
}

/// Set to a new C String (0L is OK)
UPStr& UPStr::operator=(const char* newstr)
{
	if( newstr == 0L )
		newstr = "";

	size_t l = strlen(newstr);
	if( l > kMaxChar )
		l = kMaxChar;

	char* newvalue = (char*)malloc((l + 2) * sizeof(char));
	if(newvalue == 0L)
		UTHROW(std::bad_alloc());

	strncpy(newvalue + 1, newstr, l);
	newvalue[0] = l;
	newvalue[l + 1] = '\0';
	
	clear();

	m_str = newvalue;
	
	return *this;
}

/// Set to a new P String (0L is OK)
UPStr& UPStr::operator=(const unsigned char* newstr)
{
	if( newstr == 0L )
		newstr = (unsigned char*)"";

	int l = newstr[0];
	char* newvalue = (char*)malloc((l + 2) * sizeof(char));
	if( newvalue == 0L )
		UTHROW(std::bad_alloc());

	memcpy(newvalue + 1, (char*)newstr + 1, l * sizeof(char));
	newvalue[0] = l;
	newvalue[l + 1] = '\0';

	clear();

	m_str = newvalue;

	return *this;
}

/// Set according to another UPStr
UPStr& UPStr::operator=(const UPStr& newstr)
{
	*this = (const char*)newstr;
	return *this;
}

/// Set from a buffer
UPStr& UPStr::set(const char* buf, unsigned int len)
{
	clear();
	
	if( len == 0 )
	{
		*this = "";
		return *this;
	}
	
	if( len > kMaxChar )
		len = kMaxChar;

	m_str = (char*)malloc((len + 2) * sizeof(char));
	if( m_str == 0L )
	{
		UAppConsole("Impossible to allocate %d bytes !\n", (len + 2) * sizeof(char));
		return *this;
	}

	memcpy(m_str + 1, buf, len * sizeof(char));
	m_str[0] = len;
	m_str[len + 1] = '\0';

	return *this;
}

/*!
	Replace a character
	\param which Character to be replaced
	\param bywhich Character to replace
	\return Self
*/
UPStr& UPStr::replace(char which, char bywhich)
{
	if( !empty() )
	{
		char* buf = m_str + 1;
		while( *buf )
		{
			if( *buf == which )
				*buf++ = bywhich;
			else
				buf++;
		}
	}

	return *this;
}

/// Concatenate
UPStr& UPStr::operator<<(const char* addToStr)
{
	if( addToStr == 0L )
		return *this;

	if( m_str == 0L )
	{
		*this = addToStr;
		return *this;
	}

	size_t len = strlen(addToStr);
	unsigned curlen = length();
	
	if( len + length() > kMaxChar )
		len = kMaxChar - length();

	if( len == 0 )
		return *this;

	m_str = (char*)realloc(m_str, (len + curlen + 2) * sizeof(char));
	memcpy(m_str + curlen + 1, addToStr, len * sizeof(char));
	m_str[0] = len + curlen;
	m_str[len + curlen + 1] = '\0';

	return *this;
}

/// Concatenate
UPStr& UPStr::operator<<(char addToStr)
{
	char astr[2] = {'\0', '\0'};
	astr[0] = addToStr;
	return *this << astr;
}

/// Concatenate
UPStr& UPStr::operator<<(int addToStr)
{
	char astr[50];
	sprintf(astr, "%d", addToStr);
	return *this << astr;
}

//////////////////////////////////////////////////////////////////////////
// UStr

UStr::~UStr()
{
	clear();
}

/*!
	Clear the string
*/
void UStr::clear(void)
{
	if( m_str )
	{
		free(m_str);
		m_str = 0L;
	}
}

/// Set from a C String (0L is OK)
UStr& UStr::operator=(const char* newstr)
{
	if( newstr == 0L )
		newstr = "";

	int l = strlen(newstr);
	char* newvalue = (char*)malloc((l + 1) * sizeof(char));
	if( newvalue == 0L )
		UTHROW(std::bad_alloc());

	strcpy(newvalue, newstr);

	clear();

	m_str = newvalue;

	return *this;
}

/// Set from a P String (0L is OK)
UStr& UStr::operator=(const unsigned char* newstr)
{
	if( newstr == 0L )
		newstr = (unsigned char*)"";

	int l = newstr[0];
	char* newvalue = (char*)malloc((l + 1) * sizeof(char));
	if( newvalue == 0L )
		UTHROW(std::bad_alloc());

	memcpy(newvalue, newstr + 1, l * sizeof(char));
	newvalue[l] = '\0';

	clear();

	m_str = newvalue;

	return *this;
}

/// Set according to another UStr
UStr& UStr::operator=(const UStr& newstr)
{
	*this = (const char*)newstr;
	
	return *this;
}

/// Set from a buffer
UStr& UStr::set(const char* buf, unsigned int len)
{
	clear();

	if( len == 0 )
		return *this;

	m_str = (char*)malloc((len + 1) * sizeof(char));
	if( m_str == 0L )
	{
		UAppConsole("Impossible to allocate %d bytes !\n", (len + 1) * sizeof(char));
		return *this;
	}

	memcpy(m_str, buf, len * sizeof(char));
	m_str[len] = '\0';

	return *this;
}

/*!
	Replace a character
	\param which Character to be replaced
	\param bywhich Character to replace
	\return Self
*/
UStr& UStr::replace(char which, char bywhich)
{
	if( !empty() )
	{
		char* buf = m_str;
		while( *buf )
		{
			if( *buf == which )
				*buf++ = bywhich;
			else
				buf++;
		}
	}

	return *this;
}

const UStr & UStr::Replace(const char* which, char *bywhich)
{
        if(m_str == 0L)
                return *this;

        char *buf = m_str;
        UStr newStr;
        int pos;

        while((pos = find(which)) != 0)
        {
                newStr = substr(0, pos);
                newStr += bywhich;
                newStr += substr( pos+strlen(which), length()-pos-strlen(which));

                *this = (const char *)newStr;
        }

        return *this;
}

/// Concatenate
UStr& UStr::operator<<(const char *addToStr)
{
	if( addToStr == 0L )
		return *this;

	if( m_str == 0L )
	{
		*this = addToStr;
		return *this;
	}

	unsigned int len = strlen(addToStr);
	if( len == 0 )
		return *this;

	unsigned int curlen = length();
	char* newstr = (char*)malloc((len + curlen + 1) * sizeof(char));
	if( newstr == 0L )
		UTHROW(std::bad_alloc());
	
	memcpy(newstr, m_str, curlen * sizeof(char));
	memcpy(newstr + curlen, addToStr, len * sizeof(char));
	newstr[len + curlen] = '\0';

	clear();

	m_str = newstr;

	return *this;
}



/// Concatenate
UStr& UStr::operator<<(char addToStr)
{
	char astr[2] = {'\0', '\0'};
	astr[0] = addToStr;

	return *this << astr;
}

/// Concatenate
UStr& UStr::operator<<(int addToStr)
{
	char astr[50];
	sprintf(astr, "%d", addToStr);

	return *this << astr;
}

/*!
	Find a string
	\param thestr String to be found
	\return The position of the found string, zero if not found
*/
int UStr::find(const char* thestr) const
{
	if( thestr != NULL && !empty() )
	{
		char* buf;
		buf = strstr(m_str, thestr);
		
		if( buf != NULL ) 
			return buf - m_str;
	}
	
	return 0;
}

/*!
	Find a character
	\param thechar Character to be found
	\return The position of the found character, zero if not found
*/
int UStr::find(char thechar) const
{
	if( !empty() )
	{
		char* buf;
		buf = strchr(m_str, thechar);
		
		if( buf != NULL ) 
			return buf - m_str;
	}
	
	return 0;
}

/*!
	Reverse-find a string
	\param thestr String to be found
	\return The position of the found string, zero if not found
*/
int UStr::rfind(const char* thestr) const
{
	if( thestr != NULL && !empty() )
	{
		char* buf = m_str;
		char* last_buf = NULL;
		
		while( buf )
		{
			last_buf = buf;
			buf++;
			
			buf = strstr(buf, thestr);
		}
		
		if( last_buf != NULL ) 
			return last_buf - m_str;
	}
	
	return 0;
}

/*!
	Reverse-find a character
	\param thechar Character to be found
	\return The position of the found character, zero if not found
*/
int UStr::rfind(char thechar) const
{
	if( !empty() )
	{
		char* buf;
		buf = strrchr(m_str, thechar);
		
		if( buf != NULL ) 
			return buf - m_str;
	}

	return 0;
}


#if 0
/*!
	Get substring
	\param idx Start index
	\param len Substring length
	\return Substring
*/
char* UStr::substr(int idx, int len) const
{
	char* newvalue = (char*)malloc((len + 1) * sizeof(char));
	
	if( newvalue == 0L )
		UTHROW(std::bad_alloc());
	
	for(int i = 0; i < len; i++)
	{
		newvalue[i] = m_str[i+idx];
	}
	
	newvalue[len] = '\0';
	
	return newvalue;
}
#endif

/*!
	Get substring as UStr
	\param idx Start index
	\param len Substring length
	\return new Substring
*/
UStr UStr::substr(int idx, int len) const
{
	UStr rs;

	if( !empty() )
	{
		int l = strlen (m_str);
		
		if( idx < l ) 
		{ 
			rs.set(&m_str[idx], (idx + len < l) ? len : (l - idx));
		} 
	}

	return rs;
}

/*!
	Remove all Characters matching 'c' from begin of string
	\param c Character to be searched
	\return the modified UStr
*/
UStr& UStr::trim(char c)
{
	if( !empty() )
	{
		const char *p = m_str;
		
		while( *p && (*p == c)) 
			++p;
		
		if( p != m_str)
			*this = p;
	}

	return *this;
}

/*!
	Remove all Characters matching 'c' from end of string
	\param c Character to be searched
	\return the modified UStr
*/
UStr& UStr::rtrim(char c)
{
	if( !empty() )
	{
		char* p = m_str + strlen(m_str);
		for( ; p != m_str; --p) 
		{
			if( *(p-1) != c)
				break;
		}

		*p = '\0';
	}
	
	return *this;
}

UStr& UStr::rtrim (const char *MatchList)
{
	if( !empty() )
	{
		char* p = m_str + strlen(m_str);
		for( ; p != m_str; --p) 
		{
			if( !strchr( MatchList, *(p-1)) )
				break;
		}

		*p = '\0';
	}

	return *this;
}
