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

/*
 * MultiFiles.h --- class to store multiple files by directory
 */

#include "stdafx.h"

#include <stdlib.h>
#include <string.h>

#include "MultiFiles.h"
#include "CvsArgs.h"
#include "AppConsole.h"
#include "CPStr.h"
#include "AppGlue.h"

#include <errno.h>
#include <map>
#include <utility>
#include <algorithm>

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

//////////////////////////////////////////////////////////////////////////
//

FileEntry::FileEntry()
{
}

FileEntry::FileEntry(const char* filename, const UFSSpec* macspec /*= 0L*/, const char* currRevision /*= 0L*/)
{
	file = filename;

	if( currRevision != 0L )
		currRev = currRevision;

  	if( macspec != 0L )
		spec = *macspec;
}

FileEntry::FileEntry(const FileEntry& entry)
{
	*this = entry;
}

FileEntry& FileEntry::operator=(const FileEntry& entry)
{
	file = entry.file;
	currRev = entry.currRev;
	spec = entry.spec;
	return *this;
}


//////////////////////////////////////////////////////////////////////////
// MultiFilesEntry

MultiFilesEntry::MultiFilesEntry() 
{
}

MultiFilesEntry::MultiFilesEntry(const char* path)
{
	dir = path;
}

MultiFilesEntry::MultiFilesEntry(const MultiFilesEntry& entries)
{
	*this = entries;
}

MultiFilesEntry& MultiFilesEntry::operator=(const MultiFilesEntry& entries)
{
	dir = entries.dir;
	files = entries.files;
	return *this;
}

/// Set the directory
void MultiFilesEntry::setdir(const char* newdir)
{
	dir = newdir;
}

/// Add file
void MultiFilesEntry::add(const char* file, const UFSSpec* spec, const char* currRevision)
{
	FileEntry entry(file, spec, currRevision);
	files.push_back(entry);
}

/// Get the file in the current directory, return false if failed
bool MultiFilesEntry::get(int index, CStr& path, CStr& fileName, CStr& currRev) const
{
	if( index < 0 || (size_t)index >= files.size() )
		return false;

	path = dir;
	fileName = files[index].file;
	currRev = files[index].currRev;
	
	return true;
}

/// Add the files to a CvsArgs, return the directory
const char* MultiFilesEntry::add(CvsArgs& args) const
{
	std::vector<FileEntry>::const_iterator i;
	for(i = files.begin(); i != files.end(); ++i)
	{
		args.addfile((*i).file, dir, &(*i).spec, (*i).currRev);
	}

	return dir;
}

//////////////////////////////////////////////////////////////////////////
// MultiFiles

MultiFiles::MultiFiles()
{
}

/// Add a new directory
void MultiFiles::newdir(const char* dir)
{
	MultiFilesEntry entry(dir);
	dirs.push_back(dir);
}

/// Add a new file to the current directory
void MultiFiles::newfile(const char* file, const UFSSpec* spec, const char* currRevision)
{
	if( dirs.size() == 0 )
		return;

	MultiFilesEntry& entry = dirs[dirs.size() - 1];
	entry.add(file, spec, currRevision);
}

/// Get the directory at the specified index, return false if failed
bool MultiFiles::getdir(int index, CStr& path) const
{
	if( index < 0 || index >= (int)dirs.size() )
		return false;

	const MultiFilesEntry& entry = dirs[index];

	path = entry.dir;

	return true;
}

/// Reset for reuse
void MultiFiles::reset()
{
	dirs.erase(dirs.begin(), dirs.end());
}

/// Get the total number of files
int MultiFiles::TotalNumFiles(void) const
{
	int total = 0;

	std::vector<MultiFilesEntry>::const_iterator i;
	for(i = dirs.begin(); i != dirs.end(); ++i)
	{
		total += i->NumFiles();
	}

	return total;
}

/// Split a (potentially) partial path into folder and name.
// \result returns true when the name passed was indeed a partial path
static bool split(const CStr& inPartialPath, CStr& outPath, CStr& outName)
{	
	int		delimiterPos(inPartialPath.rfind(kPathDelimiter));
	if ( delimiterPos )
	{
		outName = inPartialPath.substr(delimiterPos+1, inPartialPath.length()-delimiterPos-1);
		outPath = inPartialPath.substr(0, delimiterPos+1); // includes trailing delimiter
		return true;
	}
	else
	{
		outName = inPartialPath;
		outPath = (const char*)NULL;
		return false;
	}
}

/// helper functor to compare MultiFilesEntry by directory path
struct match_dir
{
	const CStr& dirPath;
	match_dir(const CStr& inDirPath) : dirPath(inDirPath) {}
	bool operator()(const MultiFilesEntry& inEntry) const
	{
		return dirPath == inEntry.getdir();
	}
};

/// Adjust the file list so that we end up with a list that contains no file entries with partial path names
bool MultiFiles::Normalize()
{
	std::vector<MultiFilesEntry>	workingCopy(dirs);
	dirs.clear();
	
	for ( std::vector<MultiFilesEntry>::const_iterator i(workingCopy.begin()); i != workingCopy.end(); ++i )
	  if ( i->files.empty() )
	  {
	    // special case for empty directories in MultiFiles:
	    // just add the directory to our directory list
	    //
			std::vector<MultiFilesEntry>::iterator		found;
			found = std::find_if(dirs.begin(), dirs.end(), match_dir(i->dir));
			if ( found == dirs.end() )
			{
				// directory not found in list -> create new directory
				this->newdir(i->dir);
			}
	  }
		else for ( std::vector<FileEntry>::const_iterator j(i->files.begin()); j != i->files.end(); j++ )
		{
			// split into partial path and name
			//
			CStr		fullDirPath, dir, name;
			if ( split(j->file, dir, name) ) 
			{
				// insert current directory path in front of partial path
				//
				fullDirPath = i->dir;
				if ( !i->dir.endsWith(kPathDelimiter) )
					fullDirPath << kPathDelimiter;
				fullDirPath << dir;
			}
			else fullDirPath = i->dir; // file does not have a partial path

			std::vector<MultiFilesEntry>::iterator		found;
			found = std::find_if(dirs.begin(), dirs.end(), match_dir(fullDirPath));
			if ( found != dirs.end() )
			{
				// directory already in list -> just append the new file entry
				found->add(name, &j->spec, j->currRev);
			}
			else
			{
				// directory not found in list -> create new directory and append the file to the new directory
				this->newdir(fullDirPath);
				this->newfile(name, &j->spec, j->currRev);
			}
		}

	return true;
}
