#include <efsw/DirectorySnapshot.hpp>
#include <efsw/FileSystem.hpp>

namespace efsw {

DirectorySnapshot::DirectorySnapshot()
{
}

DirectorySnapshot::DirectorySnapshot( std::string directory )
{
	init( directory );
}

DirectorySnapshot::~DirectorySnapshot()
{
}

void DirectorySnapshot::init( std::string directory )
{
	setDirectoryInfo( directory );
	initFiles();
}

bool DirectorySnapshot::exists()
{
	return DirectoryInfo.exists();
}

void DirectorySnapshot::deleteAll( DirectorySnapshotDiff& Diff )
{
	FileInfo fi;

	for ( FileInfoMap::iterator it = Files.begin(); it != Files.end(); it++ )
	{
		fi = it->second;

		if ( fi.isDirectory() )
		{
			Diff.DirsDeleted.push_back( fi );
		}
		else
		{
			Diff.FilesDeleted.push_back( fi );
		}
	}

	Files.clear();
}

void DirectorySnapshot::setDirectoryInfo( std::string directory )
{
	DirectoryInfo = FileInfo( directory );
}

void DirectorySnapshot::initFiles()
{
	Files = FileSystem::filesInfoFromPath( DirectoryInfo.Filepath );

	FileInfoMap::iterator it = Files.begin();
	std::list<std::string> eraseFiles;

	/// Remove all non regular files and non directories
	for ( ; it != Files.end(); it++ )
	{
		if ( !it->second.isRegularFile() && !it->second.isDirectory() )
		{
			eraseFiles.push_back( it->first );
		}
	}

	for ( std::list<std::string>::iterator eit = eraseFiles.begin(); eit != eraseFiles.end(); eit++ )
	{
		Files.erase( *eit );
	}
}

DirectorySnapshotDiff DirectorySnapshot::scan()
{
	DirectorySnapshotDiff	Diff;

	Diff.clear();

	FileInfo curFI( DirectoryInfo.Filepath );

	Diff.DirChanged	= DirectoryInfo != curFI;

	if ( Diff.DirChanged )
	{
		DirectoryInfo = curFI;
	}

	/// If the directory was erased, create the events for files and directories deletion
	if ( !curFI.exists() )
	{
		deleteAll( Diff );

		return Diff;
	}

	FileInfoMap files = FileSystem::filesInfoFromPath( DirectoryInfo.Filepath );

	if ( files.empty() && Files.empty() )
	{
		return Diff;
	}

	FileInfo fi;
	FileInfoMap FilesCpy;
	FileInfoMap::iterator it;
	FileInfoMap::iterator fiIt;

	if ( Diff.DirChanged )
	{
		FilesCpy = Files;
	}

	for ( it = files.begin(); it != files.end(); it++ )
	{
		fi	= it->second;

		/// File existed before?
		fiIt = Files.find( it->first );

		if ( fiIt != Files.end() )
		{
			/// Erase from the file list copy
			FilesCpy.erase( it->first );

			/// File changed?
			if ( (*fiIt).second != fi )
			{
				/// Update the new file info
				Files[ it->first ] = fi;

				/// handle modified event
				if ( fi.isDirectory() )
				{
					Diff.DirsModified.push_back( fi );
				}
				else
				{
					Diff.FilesModified.push_back( fi );
				}
			}
		}
		/// Only add regular files or directories
		else if ( fi.isRegularFile() || fi.isDirectory() )
		{
			/// New file found
			Files[ it->first ] = fi;

			FileInfoMap::iterator fit;
			std::string oldFile = "";

			/// Check if the same inode already existed
			if ( ( fit = nodeInFiles( fi ) ) != Files.end() )
			{
				oldFile = fit->first;

				/// Avoid firing a Delete event
				FilesCpy.erase( fit->first );

				/// Delete the old file name
				Files.erase( fit->first );

				if ( fi.isDirectory() )
				{
					Diff.DirsMoved.push_back( std::make_pair( oldFile, fi ) );
				}
				else
				{
					Diff.FilesMoved.push_back( std::make_pair( oldFile, fi ) );
				}
			}
			else
			{
				if ( fi.isDirectory() )
				{
					Diff.DirsCreated.push_back( fi );
				}
				else
				{
					Diff.FilesCreated.push_back( fi );
				}
			}
		}
	}

	if ( !Diff.DirChanged )
	{
		return Diff;
	}

	/// The files or directories that remains were deleted
	for ( it = FilesCpy.begin(); it != FilesCpy.end(); it++ )
	{
		fi = it->second;

		if ( fi.isDirectory() )
		{
			Diff.DirsDeleted.push_back( fi );
		}
		else
		{
			Diff.FilesDeleted.push_back( fi );
		}

		/// Remove the file or directory from the list of files
		Files.erase( it->first );
	}

	return Diff;
}

FileInfoMap::iterator DirectorySnapshot::nodeInFiles( FileInfo& fi )
{
	FileInfoMap::iterator it;

	if ( FileInfo::inodeSupported() )
	{
		for ( it = Files.begin(); it != Files.end(); it++ )
		{
			if ( it->second.sameInode( fi ) && it->second.Filepath != fi.Filepath )
			{
				return it;
			}
		}
	}

	return Files.end();
}

void DirectorySnapshot::addFile( std::string path )
{
	std::string name( FileSystem::fileNameFromPath( path ) );
	Files[ name ] = FileInfo( path );
}

void DirectorySnapshot::removeFile( std::string path )
{
	std::string name( FileSystem::fileNameFromPath( path ) );

	FileInfoMap::iterator it = Files.find( name );

	if ( Files.end() != it )
	{
		Files.erase( it );
	}
}

void DirectorySnapshot::moveFile( std::string oldPath, std::string newPath )
{
	removeFile( oldPath );
	addFile( newPath );
}

void DirectorySnapshot::updateFile(std::string path)
{
	addFile( path );
}

}
