#pragma once
#include <panda/unievent.h>
#include <panda/net/sockaddr.h>
#include <panda/CallbackDispatcher.h>

namespace panda { namespace unievent { namespace test {

template <typename T>
using sp = iptr<T>;

using panda::string;

struct AsyncTest {
    using SockAddr = panda::net::SockAddr;
    LoopSP              loop;
    std::vector<string> expected;
    std::vector<string> happened;
    size_t              counter = 0;
    TimerSP             timer;

    struct Error : std::runtime_error {
        Error(std::string msg, AsyncTest& test);
    };

    static SockAddr get_refused_addr   ();
    static SockAddr get_blackhole_addr ();

    AsyncTest (uint64_t timeout, unsigned count = 0, const LoopSP& loop = nullptr);
    AsyncTest (uint64_t timeout, const std::vector<string>& expected, const LoopSP& loop = nullptr);
    virtual ~AsyncTest() noexcept(false);

    void set_expected (unsigned);
    void set_expected (const std::vector<string>&);

    bool run        ();
    bool run_once   ();
    bool run_nowait ();

    void happens (string event = "<event>");

    template <typename Ret, typename...Args>
    void happens_when (CallbackDispatcher<Ret(Args...)>& dispatcher, string event = "<event>") {
        dispatcher.add([this, event](auto&&...){
            happens(event);
        });
    }

    template <typename Ret, typename...Args>
    void count_events (CallbackDispatcher<Ret(Args...)>& dispatcher) {
        dispatcher.add([this](auto&&...){
            counter++;
        });
    }

    template <class T> static inline T _await_copy (T arg) { return arg; }
    static inline std::error_code _await_copy (const std::error_code& err) { return err; }

    template <typename Ret, typename... Args, typename Dispatcher = CallbackDispatcher<Ret(Args...)>>
    std::tuple<decltype(_await_copy(std::declval<Args>()))...>
    await (CallbackDispatcher<Ret(Args...)>& dispatcher, string event = "") {
        using Callback = typename Dispatcher::Callback;
        std::tuple<decltype(_await_copy(std::declval<Args>()))...> result;
        Callback wrapper = [&](typename Dispatcher::Event& e, Args... args) {
            loop->stop();
            e.dispatcher.remove(wrapper);
            result = std::make_tuple(_await_copy(args)...);
            happens(event);
            e.next(args...);
        };
        dispatcher.add_event_listener(wrapper);

        run();
        return result;
    }

    template <typename... Args>
    std::tuple<decltype(_await_copy(std::declval<Args>()))...>
    await (panda::function<void(Args...)>& cb, string event = "") {
        using Function = panda::function<void(Args...)>;
        std::tuple<decltype(_await_copy(std::declval<Args>()))...> result;
        Function prev = cb;
        Function wrapper = [&](Args... args) -> void {
            loop->stop();
            result = std::make_tuple(_await_copy(args)...);
            happens(event);
            if (prev) {
                return prev(args...);
            } else {
                return;
            }
        };
        cb = wrapper;
        run();
        cb = prev;
        return result;
    }

    template <typename Ret, typename... Args>
    void await (const std::vector<CallbackDispatcher<Ret(Args...)>*>& v, string event = "") {
        size_t cnt = v.size();

        auto action = [&]() {
            if (--cnt) return;
            loop->stop();
            happens(event);
        };

        for (auto d : v) wrap_dispatcher(*d, action);

        run();
    }

    template <typename... Dispatchers>
    void await_multi (Dispatchers&... dispatchers) {
        size_t counter = sizeof...(dispatchers);
        auto action = [&]() {
            if (--counter == 0) {
                loop->stop();
            }
        };

        auto fake = {wrap_dispatcher(dispatchers, action)...}; (void)fake;
        run();
    }

    /** return true if callback was not called **/
    template <typename T>
    bool await_not (T&& f, uint64_t timeout) {
        bool by_timer = false;
        TimerSP timer = Timer::create_once(timeout, [&](Timer*) {
            by_timer = true;
            loop->stop();
        }, loop); (void)timer;

        await(f);
        return by_timer;
    }

    bool wait (uint64_t timeout);

protected:
    virtual std::string generate_report ();
    string destroy_loop (); // return error if smth foes wrong

private:
    template <typename Action, typename Ret, typename... Args, typename Dispatcher = CallbackDispatcher<Ret(Args...)>>
    int wrap_dispatcher (CallbackDispatcher<Ret(Args...)>& dispatcher, Action& action) {
        typename Dispatcher::Callback wrapper(
            [&](Ifunction<Ret, typename Dispatcher::Event&, Args...>& self, typename Dispatcher::Event& e, Args... args) {
                e.dispatcher.remove(self);
                action();
                e.next(args...);
            }
        );
        dispatcher.add_event_listener(wrapper);
        return 1; // for fake foreach
    }

    sp<Timer> create_timeout (uint64_t timeout);
    bool happened_as_expected ();
};

}}}