#ifndef WF_SAFE_LIST_HPP
#define WF_SAFE_LIST_HPP

#include <list>
#include <memory>
#include <algorithm>
#include <functional>

#include <wayland-server.h>

#include "reverse.hpp"

/* This is a trimmed-down wrapper of std::list<T>.
 *
 * It supports safe iteration over all elements in the collection, where any
 * element can be deleted from the list at any given time (i.e even in a
 * for-each-like loop) */
namespace wf
{
/* The object type depends on the safe list type, and the safe list type
 * needs access to the event loop. However, the event loop is typically
 * available from core.hpp. Because we can't resolve this circular dependency,
 * we provide a link to the event loop specially for the safe list */
namespace _safe_list_detail
{
/* In main.cpp, and initialized there */
extern wl_event_loop *event_loop;
void idle_cleanup_func(void *data);
}

template<class T>
class safe_list_t
{
    std::list<std::unique_ptr<T>> list;
    wl_event_source *idle_cleanup_source = NULL;

    /* Remove all invalidated elements in the list */
    std::function<void()> do_cleanup = [&] ()
    {
        auto it = list.begin();
        while (it != list.end())
        {
            if (*it)
            {
                ++it;
            } else
            {
                it = list.erase(it);
            }
        }

        idle_cleanup_source = NULL;
    };

    /* Return whether the list has invalidated elements */
    bool is_dirty() const
    {
        return idle_cleanup_source;
    }

  public:
    safe_list_t()
    {}

    /* Copy the not-erased elements from other, but do not copy the idle source */
    safe_list_t(const safe_list_t& other)
    {
        *this = other;
    }

    safe_list_t& operator =(const safe_list_t& other)
    {
        this->idle_cleanup_source = NULL;
        other.for_each([&] (auto& el)
        {
            this->push_back(el);
        });
    }

    safe_list_t(safe_list_t&& other) = default;
    safe_list_t& operator =(safe_list_t&& other) = default;

    ~safe_list_t()
    {
        if (idle_cleanup_source)
        {
            wl_event_source_remove(idle_cleanup_source);
        }
    }

    T& back()
    {
        /* No invalidated elements */
        if (!is_dirty())
        {
            return *list.back();
        }

        auto it = list.rbegin();
        while (it != list.rend() && (*it) == nullptr)
        {
            ++it;
        }

        if (it == list.rend())
        {
            throw std::out_of_range("back() called on an empty list!");
        }

        return **it;
    }

    size_t size() const
    {
        if (!is_dirty())
        {
            return list.size();
        }

        /* Count non-null elements, because that's the real size */
        size_t sz = 0;
        for (auto& it : list)
        {
            sz += (it != nullptr);
        }

        return sz;
    }

    /* Push back by copying */
    void push_back(T value)
    {
        list.push_back(std::make_unique<T>(std::move(value)));
    }

    /* Push back by moving */
    void emplace_back(T&& value)
    {
        list.push_back(std::make_unique<T>(value));
    }

    enum insert_place_t
    {
        INSERT_BEFORE,
        INSERT_AFTER,
        INSERT_NONE,
    };

    /* Insert the given value at a position in the list, determined by the
     * check function. The value is inserted at the first position that
     * check indicates, or at the end of the list otherwise */
    void emplace_at(T&& value, std::function<insert_place_t(T&)> check)
    {
        auto it = list.begin();
        while (it != list.end())
        {
            /* Skip empty elements */
            if (*it == nullptr)
            {
                ++it;
                continue;
            }

            auto place = check(**it);
            switch (place)
            {
              case INSERT_AFTER:
                /* We can safely increment it, because it points to an
                 * element in the list */
                ++it;

              // fall through
              case INSERT_BEFORE:
                list.emplace(it, std::make_unique<T>(value));

                return;

              default:
                break;
            }

            ++it;
        }

        /* If no place found, insert at the end */
        emplace_back(std::move(value));
    }

    void insert_at(T value, std::function<insert_place_t(T&)> check)
    {
        emplace_at(std::move(value), check);
    }

    /* Call func for each non-erased element of the list */
    void for_each(std::function<void(T&)> func) const
    {
        /* Go through all elements currently in the list */
        auto it = list.begin();
        for (int size = list.size(); size > 0; size--, it++)
        {
            if (*it)
            {
                func(**it);
            }
        }
    }

    /* Call func for each non-erased element of the list in reversed order */
    void for_each_reverse(std::function<void(T&)> func) const
    {
        auto it = list.rbegin();
        for (int size = list.size(); size > 0; size--, it++)
        {
            if (*it)
            {
                func(**it);
            }
        }
    }

    /* Safely remove all elements equal to value */
    void remove_all(const T& value)
    {
        remove_if([=] (const T& el) { return el == value; });
    }

    /* Remove all elements from the list */
    void clear()
    {
        remove_if([] (const T& el) { return true; });
    }

    /* Remove all elements satisfying a given condition.
     * This function resets their pointers and scheduling a cleanup operation */
    void remove_if(std::function<bool(const T&)> predicate)
    {
        bool actually_removed = false;
        for (auto& it : list)
        {
            if (it && predicate(*it))
            {
                actually_removed = true;
                /* First reset the element in the list, and then free resources */
                auto copy = std::move(it);
                it = nullptr;
                /* Now copy goes out of scope */
            }
        }

        /* Schedule a clean-up, but be careful to not schedule it twice */
        if (!idle_cleanup_source && actually_removed)
        {
            idle_cleanup_source = wl_event_loop_add_idle(
                _safe_list_detail::event_loop,
                _safe_list_detail::idle_cleanup_func, &do_cleanup);
        }
    }
};
}

#endif /* end of include guard: WF_SAFE_LIST_HPP */
