/**
 * @file
 * @author Nicholas Logozzo <nlogozzo225@gmail.com>
 *
 * @section LICENSE
 *
 * 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 3 of
 * the License, 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 at
 * https://www.gnu.org/copyleft/gpl.html
 *
 * @section DESCRIPTION
 *
 * A watcher of a file system folder.
 */

#ifndef FILESYSTEMWATCHER_H
#define FILESYSTEMWATCHER_H

#include <filesystem>
#include <mutex>
#include <thread>
#include <vector>
#include "filesystemchangedeventargs.h"
#include "watcherflags.h"
#include "events/event.h"
#ifdef _WIN32
#include <windows.h>
#elif defined(__APPLE__)
#include <CoreServices/CoreServices.h>
#endif

namespace Nickvision::Filesystem
{
    /**
     * @brief A watcher of a file system folder.
     */
    class FileSystemWatcher
    {
    public:
        /**
         * @brief Constructs a FileSystemWatcher.
         * @param path The path of the folder to watch
         * @param includeSubdirectories Whether or not to include subdirectories for the folder
         * @param watcherFlags The flags of what to watch changes for
         * @exception std::runtime_error Thrown if unable to initialize watcher
         */
        FileSystemWatcher(const std::filesystem::path& path, bool includeSubdirectories, WatcherFlags watcherFlags = WatcherFlags::FileName | WatcherFlags::DirectoryName | WatcherFlags::Attributes | WatcherFlags::Size | WatcherFlags::LastWrite | WatcherFlags::LastAccess);
        /**
         * @brief Destructs a FileSystemWatcher. 
         */
        ~FileSystemWatcher() noexcept;
        /**
         * @brief Gets the path of the folder being watched.
         * @return The path of the folder being watched
         */
        const std::filesystem::path& getPath() const noexcept;
        /**
         * @brief Gets the flags of what to watch changes for.
         * @return The flags of watched properties
         */
        WatcherFlags getWatcherFlags() const noexcept;
        /**
         * @brief Gets whether or not subdirectories of the folder are watched.
         * @return True if subdirectories watched, else false
         */
        bool getIncludeSubdirectories() const noexcept;
        /**
         * @brief Gets the event for when a watched flag of the folder is changed.
         * @return The changed event
         */
        Events::Event<FileSystemChangedEventArgs>& changed() noexcept;
        /**
         * @brief Gets whether or not a file extension is being watched.
         * @param extension The file extension to check
         * @return True if extension is being watched, else false
         * @return True if list of extension filters is empty
         */
        bool isExtensionWatched(const std::filesystem::path& extension) const noexcept;
        /**
         * @brief Adds an extension of a file to watch for changes in the folder.
         * @param extension The file extension to add
         * @return True if successful, else false
         */
        bool addExtensionFilter(const std::filesystem::path& extension) noexcept;
        /**
         * @brief Removes an extension of a file to watch for changes in the folder.
         * @param extension The file extension to remove
         * @return True if successful, else false
         */
        bool removeExtensionFilter(const std::filesystem::path& extension) noexcept;
        /**
         * @brief Clears all watched extension filters. This will cause all extensions to be implicitly watched.
         * @return True if successful, else false
         */
        bool clearExtensionFilters() noexcept;

    private:
        /**
         * @brief Runs the loop to watch a folder for changes.
         */
        void watch() noexcept;
        std::thread m_watchThread;
        mutable std::mutex m_mutex;
        std::filesystem::path m_path;
        bool m_includeSubdirectories;
        WatcherFlags m_watcherFlags;
        Events::Event<FileSystemChangedEventArgs> m_changed;
        bool m_watching;
        std::vector<std::filesystem::path> m_extensionFilters;
#ifdef _WIN32
        HANDLE m_terminateEvent;
#elif defined(__linux__)
        int m_notify;
#elif defined(__APPLE__)
        static void callback(ConstFSEventStreamRef stream, void* clientCallBackInfo, size_t numEvents, void* eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[]) noexcept;
        FSEventStreamRef m_stream;
        CFRunLoopRef m_runLoop;
#endif
    };
}

#endif //FILESYSTEMWATCHER_H