/*==============================================================================

Copyright 2018 by Roland Rabien
For more information visit www.rabiensoftware.com

==============================================================================*/

#pragma once

#if defined JUCE_MAC || defined JUCE_WINDOWS || defined JUCE_LINUX

/**

    Watches a folder in the file system for changes.

    Listener callbcks will be called every time a file is
    created, modified, deleted or renamed in the watched
    folder.

    FileSystemWatcher will also recursively watch all subfolders on
    macOS and windows and will not on Linux.

 */
class FileSystemWatcher
{
public:
    //==============================================================================
    FileSystemWatcher();
    ~FileSystemWatcher();

    //==============================================================================
    /** All events that arrive withing the time window for a particular file will be
        coalesced into one event with the type of the most recent event */
    void coalesceEvents (int windowMS);

    /** Adds a folder to be watched */
    void addFolder (const juce::File& folder);

    /** Removes a folder from being watched */
    void removeFolder (const juce::File& folder);

    /** Removes all folders from being watched */
    void removeAllFolders();

    /** Gets a list of folders being watched */
    juce::Array<juce::File> getWatchedFolders ();

    /** A set of events that can happen to a file.
        When a file is renamed it will appear as the
        original filename being deleted and the new
        filename being created
    */
    enum FileSystemEvent
    {
        undefined,
        fileCreated,
        fileDeleted,
        fileUpdated,
        fileRenamedOldName,
        fileRenamedNewName
    };

    //==============================================================================
    /** Receives callbacks from the FileSystemWatcher when a file changes */
    class Listener
    {
    public:
        virtual ~Listener() = default;

        /* Called when any file in the listened to folder changes with the name of
           the folder that has changed. For example, use this for a file browser that
           needs to refresh any time a file changes */
        virtual void folderChanged (const juce::File&) {}

        /* Called for each file that has changed and how it has changed. Use this callback
           if you need to reload a file when it's contents change */
        virtual void fileChanged (const juce::File&, FileSystemEvent) {}
    };

    /** Registers a listener to be told when things happen to the text.
     @see removeListener
     */
    void addListener (Listener* newListener);

    /** Deregisters a listener.
     @see addListener
     */
    void removeListener (Listener* listener);

private:
    class Impl;

    void folderChanged (const juce::File& folder);
    void fileChanged (const juce::File& file, FileSystemEvent fsEvent);

    int coalesceWindowMS = 0;

    struct CoalesceTimer : public juce::Timer
    {
        CoalesceTimer (FileSystemWatcher& o, juce::File f_)
            : owner (o), f (f_), folder (true)
        {
        }

        CoalesceTimer (FileSystemWatcher& o, juce::File f_, FileSystemEvent e_)
            : owner (o), f (f_), folder (false), fsEvent (e_)
        {
        }

        void timerCallback() override
        {
            stopTimer();

            if (folder)
                owner.listeners.call (&FileSystemWatcher::Listener::folderChanged, f);
            else
                owner.listeners.call (&FileSystemWatcher::Listener::fileChanged, f, fsEvent);

            owner.timers.erase (f);
        }

        FileSystemWatcher& owner;

        juce::File f;
        bool folder;
        FileSystemEvent fsEvent;
    };

    std::map<juce::File, std::unique_ptr<CoalesceTimer>> timers;

    juce::ListenerList<Listener> listeners;

    juce::OwnedArray<Impl> watched;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileSystemWatcher)
    JUCE_DECLARE_WEAK_REFERENCEABLE (FileSystemWatcher)
};

#endif
