#include "FsPoll.h"
using namespace panda::unievent;

const HandleType FsPoll::TYPE("fs_poll");

FsPollSP FsPoll::create (string_view path, unsigned int interval, const fs_poll_fn& cb, const LoopSP& loop) {
    FsPollSP h = new FsPoll(loop);
    h->start(path, interval, cb);
    return h;
}

FsPoll::FsPoll (const LoopSP& loop) : prev(), fetched(), _listener() {
    _init(loop);
    fsr   = new Fs::Request(loop);
    timer = new Timer(loop);
    timer->event.add([this](auto) {
        if (fsr->active()) return; // filesystem has not yet completed the request -> skip one cycle
        this->do_stat();
    });
}

const HandleType& FsPoll::type () const {
    return TYPE;
}

void FsPoll::start (string_view path, unsigned int interval, const fs_poll_fn& callback) {
    if (timer->active()) throw Error("cannot start FsPoll: it is already active");
    if (callback) poll_event.add(callback);
    _path = string(path);
    timer->start(interval);
    if (fsr->active()) fsr = new Fs::Request(loop()); // previous fspoll task has not yet been completed -> forget FSR and create new one
    do_stat();
}

void FsPoll::stop () {
    if (!timer->active()) return;
    timer->stop();
    prev = Fs::FStat();
    // if cancellation possible it will call callback (which we will ignore)
    // otherwise nothing will happen and fsr will remain busy (and if it is not complete by the next start(), we will change fsr
    fsr->cancel();
    fetched = false;
}

void FsPoll::do_stat () {
    if (!wself) wself = FsPollSP(this);
    auto wp = wself;
    fsr->stat(_path, [this, wp](auto& stat, auto& err, const Fs::RequestSP& req) {
        auto p = wp.lock();
        if (!p) return; // check if <this> is dead by the moment, after this line we can safely use 'this'
        if (!timer->active() || fsr != req) return; // ongoing previous result -> ignore

        opt<Fs::FStat> prev_opt, stat_opt;
        if (!prev_err) prev_opt = prev;
        if (!err) stat_opt = stat;

        if (err) {
            if (err != prev_err) {
                prev_err = err;
                // accessing <stat> is UB
                if (!fetched) this->initial_notify(stat_opt, err);
                this->notify(prev_opt, stat_opt, err);
            }
        }
        else if (!fetched || prev != stat) {
            if (fetched) this->notify(prev_opt, stat_opt, err);
            else         this->initial_notify(stat_opt, err);
            prev = stat;
        }

        fetched = true;
    });
}

void FsPoll::reset () {
    stop();
}

void FsPoll::clear () {
    stop();
    weak(false);
    _listener = nullptr;
    poll_event.remove_all();
    start_event.remove_all();
}

void FsPoll::notify (const opt<Fs::FStat>& prev, const opt<Fs::FStat>& cur, const std::error_code& err) {
    FsPollSP self = this;
    poll_event(self, prev, cur, err);
    if (_listener) _listener->on_fs_poll(self, prev, cur, err);
}

void FsPoll::initial_notify (const opt<Fs::FStat>& cur, const std::error_code& err) {
    FsPollSP self = this;
    start_event(self, cur, err);
    if (_listener) _listener->on_fs_start(self, cur, err);
}