// =============================================================== //
//                                                                 //
//   File      : FileWatch.h                                       //
//   Purpose   : Watch awar pointing to file AND file itself       //
//                                                                 //
//   Coded by Ralf Westram (coder@reallysoft.de) in October 2017   //
//   http://www.arb-home.de/                                       //
//                                                                 //
// =============================================================== //

#ifndef FILEWATCH_H
#define FILEWATCH_H

#ifndef AW_AWAR_HXX
#include <aw_awar.hxx>
#endif
#ifndef AW_ROOT_HXX
#include <aw_root.hxx>
#endif
#ifndef AW_INOTIFY_HXX
#include <aw_inotify.hxx>
#endif
#ifndef ARB_FILE_H
#include <arb_file.h>
#endif

/*  Binds user-callback to an awar which contains a filename (full-path; e.g. from File_selection)
 *  - the user-callback will be called whenever the awar triggers
 *  - the awar will automatically change when
 *    - it points to a file and the file gets modified (awar gets touched)
 *    - the file is deleted (awar changes to "")
 *    - the file is re-created (if awar is "" -> awar points to file again)
 */

class FileWatch : virtual Noncopyable {
    AW_awar             *awar_selfile; // contains name of file (or sth invalid like "")
    SmartCharPtr         watched_file; // if set -> currently watched file
    FileChangedCallback  wf_cb;        // used to watch file changes
    FileChangedCallback  user_cb;      // called if awar or (pointed-to) file changes
    // Warning: be aware that callbacks may be called with empty name (""),
    //          depending on where awar is used!

    void file_modified_handler(const char *file, ChangeReason reason) const {
        // - corrects "selected file" in response to filesystem changes (de- and re-select)
        // - triggers awar callback if file gets modified

        switch (reason) {
            case CR_CREATED:
            case CR_MODIFIED:
                if (awar_selfile->read_char_pntr()[0]) { // awar has content (do not overwrite)
                    awar_selfile->touch();
                }
                else if (GB_is_regularfile(file)) {
                    awar_selfile->write_string(file);
                }
                break;
            case CR_DELETED:
                aw_assert(!GB_is_regularfile(file));
                awar_selfile->write_string("");
                break;
        }

    }

    void remove_inotification() {
        if (watched_file.isSet()) {
            AW_remove_inotification(&*watched_file, wf_cb);
            watched_file.setNull();
        }
    }

    void awar_modified_handler() {
        SmartCharPtr file(awar_selfile->read_string());

        bool points_to_file    = GB_is_regularfile(&*file);
        bool need_change_watch =
            points_to_file
            ? (watched_file.isNull() || strcmp(&*watched_file, &*file) != 0)
            : watched_file.isSet();

        if (need_change_watch) {
            remove_inotification();
            if (points_to_file) {
                watched_file = file;
                AW_add_inotification(&*watched_file, wf_cb);
            }
        }

        user_cb(&*file, CR_MODIFIED);
    }

    // callback-wrappers
    static void file_modified_wrapper(const char *file, ChangeReason reason, FileWatch *watch) { watch->file_modified_handler(file, reason); }
    static void awar_modified_wrapper(AW_root*, FileWatch *watch) { watch->awar_modified_handler(); }

public:

    FileWatch(const char *sel_file_awar, const FileChangedCallback& file_changed_cb) :
        awar_selfile(AW_root::SINGLETON->awar(sel_file_awar)),
        wf_cb(makeFileChangedCallback(file_modified_wrapper, this)),
        user_cb(file_changed_cb)
    {
        awar_selfile->add_callback(makeRootCallback(awar_modified_wrapper, this));
    }

    ~FileWatch() {
        awar_selfile->remove_callback(makeRootCallback(awar_modified_wrapper, this));
        remove_inotification();
    }
};

#else
#error FileWatch.h included twice
#endif // FILEWATCH_H
