#include <efsw/FileWatcherKqueue.hpp>

#if EFSW_PLATFORM == EFSW_PLATFORM_KQUEUE || EFSW_PLATFORM == EFSW_PLATFORM_FSEVENTS

#include <sys/time.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <string.h>
#include <efsw/FileSystem.hpp>
#include <efsw/System.hpp>
#include <efsw/Debug.hpp>
#include <efsw/WatcherGeneric.hpp>
#include <efsw/Lock.hpp>

namespace efsw
{

FileWatcherKqueue::FileWatcherKqueue( FileWatcher * parent ) :
	FileWatcherImpl( parent ),
	mLastWatchID(0),
	mThread( NULL ),
	mFileDescriptorCount( 1 ),
	mAddingWatcher( false )
{
	mTimeOut.tv_sec		= 0;
	mTimeOut.tv_nsec	= 0;
	mInitOK				= true;
}

FileWatcherKqueue::~FileWatcherKqueue()
{
	WatchMap::iterator iter = mWatches.begin();

	for(; iter != mWatches.end(); ++iter)
	{
		efSAFE_DELETE( iter->second );
	}

	mWatches.clear();

	mInitOK = false;

	efSAFE_DELETE( mThread );
}

WatchID FileWatcherKqueue::addWatch(const std::string& directory, FileWatchListener* watcher, bool recursive)
{
	static bool s_ug = false;

	std::string dir( directory );

	FileSystem::dirAddSlashAtEnd( dir );

	FileInfo fi( dir );

	if ( !fi.isDirectory() )
	{
		return Errors::Log::createLastError( Errors::FileNotFound, dir );
	}
	else if ( !fi.isReadable() )
	{
		return Errors::Log::createLastError( Errors::FileNotReadable, dir );
	}
	else if ( pathInWatches( dir ) )
	{
		return Errors::Log::createLastError( Errors::FileRepeated, directory );
	}

	std::string curPath;
	std::string link( FileSystem::getLinkRealPath( dir, curPath ) );

	if ( "" != link )
	{
		if ( pathInWatches( link ) )
		{
			return Errors::Log::createLastError( Errors::FileRepeated, directory );
		}
		else if ( !linkAllowed( curPath, link ) )
		{
			return Errors::Log::createLastError( Errors::FileOutOfScope, dir );
		}
		else
		{
			dir = link;
		}
	}

	/// Check first if are enough file descriptors available to create another kqueue watcher, otherwise it creates a generic watcher
	if ( availablesFD() )
	{
		mAddingWatcher = true;

		WatcherKqueue * watch = new WatcherKqueue( ++mLastWatchID, dir, watcher, recursive, this );

		{
			Lock lock( mWatchesLock );
			mWatches.insert(std::make_pair(mLastWatchID, watch));
		}

		watch->addAll();

		// if failed to open the directory... erase the watcher
		if ( !watch->initOK() )
		{
			int le = watch->lastErrno();

			mWatches.erase( watch->ID );

			efSAFE_DELETE( watch );

			mLastWatchID--;

			// Probably the folder has too many files, create a generic watcher
			if ( EACCES != le )
			{
				WatcherGeneric * genericWatch = new WatcherGeneric( ++mLastWatchID, dir, watcher, this, recursive );

				Lock lock( mWatchesLock );
				mWatches.insert(std::make_pair(mLastWatchID, genericWatch));
			}
			else
			{
				return Errors::Log::createLastError( Errors::Unspecified, link );
			}
		}

		mAddingWatcher = false;
	}
	else
	{
		if ( !s_ug )
		{
			efDEBUG( "Started using generic watcher, file descriptor limit reached: %ld\n", mFileDescriptorCount );
			s_ug = true;
		}

		WatcherGeneric * watch = new WatcherGeneric( ++mLastWatchID, dir, watcher, this, recursive );

		Lock lock( mWatchesLock );
		mWatches.insert(std::make_pair(mLastWatchID, watch));
	}

	return mLastWatchID;
}

void FileWatcherKqueue::removeWatch(const std::string& directory)
{
	Lock lock( mWatchesLock );

	WatchMap::iterator iter = mWatches.begin();

	for(; iter != mWatches.end(); ++iter)
	{
		if(directory == iter->second->Directory)
		{
			removeWatch(iter->first);
			return;
		}
	}
}

void FileWatcherKqueue::removeWatch(WatchID watchid)
{
	Lock lock( mWatchesLock );

	WatchMap::iterator iter = mWatches.find(watchid);

	if(iter == mWatches.end())
		return;

	Watcher* watch = iter->second;

	mWatches.erase(iter);

	efSAFE_DELETE( watch );
}

bool FileWatcherKqueue::isAddingWatcher() const
{
	return mAddingWatcher;
}

void FileWatcherKqueue::watch()
{
	if ( NULL == mThread )
	{
		mThread = new Thread( &FileWatcherKqueue::run, this );
		mThread->launch();
	}
}

void FileWatcherKqueue::run()
{
	do
	{
		{
			Lock lock( mWatchesLock );

			for ( WatchMap::iterator it = mWatches.begin(); it != mWatches.end(); ++it )
			{
				it->second->watch();
			}
		}

		System::sleep( 500 );
	} while( mInitOK );
}

void FileWatcherKqueue::handleAction(Watcher* watch, const std::string& filename, unsigned long action, std::string oldFilename)
{
}

std::list<std::string> FileWatcherKqueue::directories()
{
	std::list<std::string> dirs;

	Lock lock( mWatchesLock );

	WatchMap::iterator it = mWatches.begin();

	for ( ; it != mWatches.end(); ++it )
	{
		dirs.push_back( it->second->Directory );
	}

	return dirs;
}

bool FileWatcherKqueue::pathInWatches( const std::string& path )
{
	WatchMap::iterator it = mWatches.begin();

	for ( ; it != mWatches.end(); ++it )
	{
		if ( it->second->Directory == path )
		{
			return true;
		}
	}

	return false;
}

void FileWatcherKqueue::addFD()
{
	mFileDescriptorCount++;
}

void FileWatcherKqueue::removeFD()
{
	mFileDescriptorCount--;
}

bool FileWatcherKqueue::availablesFD()
{
	return mFileDescriptorCount <= (Int64)System::getMaxFD() - 500;
}

}

#endif
