#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 );
}

} // namespace efsw
