/**
 * \file imperative/python/src/intrusive_list.h
 * MegEngine is Licensed under the Apache License, Version 2.0 (the "License")
 *
 * Copyright (c) 2014-2021 Megvii Inc. All rights reserved.
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT ARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 */

#include "megbrain/utils/metahelper.h"

namespace mgb::imperative::python::intrusive_list {

// copy policy
struct after_t {};
struct before_t {};
struct disable_t {};

template <typename T> struct Tail;

// invariant: next->prev == this
template <typename T>
struct Head {
    Tail<T>* next;

    Head(Tail<T>* node = nullptr) : next(node) {}
    Head(const Head<T>&) = delete;
    Head<T>& operator=(const Head<T>&) = delete;
    Head(Head<T>&& rhs) : next(rhs.next) {
        rhs.next = nullptr;
        if (next) {
            next->prev = this;
        }
    }
    Head<T>& operator=(Head<T>&& rhs) {
        mgb_assert(!next);
        next = rhs.next;
        rhs.next = nullptr;
        if (next) {
            next->prev = this;
        }
        return *this;
    }
    ~Head() {
        if (next) {
            next->prev = nullptr;
        }
    }
};

// invariant: prev->next == this
template <typename T>
struct Tail {
    Head<T>* prev;

    Tail(Head<T>* node = nullptr) : prev(node) {}
    Tail(const Tail<T>&) = delete;
    Tail<T>& operator=(const Tail<T>&) = delete;
    Tail(Tail<T>&& rhs) : prev(rhs.prev) {
        rhs.prev = nullptr;
        if (prev) {
            prev->next = this;
        }
    }
    Tail<T>& operator=(Tail<T>&& rhs) {
        mgb_assert(!prev);
        prev = rhs.prev;
        rhs.prev = nullptr;
        if (prev) {
            prev->next = this;
        }
        return *this;
    }
    ~Tail() {
        if (prev) {
            prev->next = nullptr;
        }
    }
};

template <typename T, typename policy> struct Node;

template <typename T>
class Iterator {
    T* ptr;

    void inc() {ptr = static_cast<T*>(ptr->Head<T>::next);}
    void dec() {ptr = static_cast<T*>(ptr->Head<T>::prev);}

public:
    Iterator(Head<T>& head) : ptr(static_cast<T*>(head.next)) {}
    Iterator(Tail<T>& tail) : ptr(static_cast<T*>(tail.prev)) {}

    template<typename policy>
    Iterator(Node<T, policy>& node) : ptr(static_cast<T*>(&node)) {}

    T& operator*() {return *static_cast<T*>(ptr);}
    T* operator->() {return static_cast<T*>(ptr);}

    operator bool() {return ptr;}
    bool operator==(const Iterator<T>& rhs) {return ptr == rhs.ptr;}

    Iterator& operator++() {inc(); return *this;}
    Iterator& operator--() {dec(); return *this;}
    Iterator operator++(int) {auto ret = *this; inc(); return ret;}
    Iterator operator--(int) {auto ret = *this; dec(); return ret;}
};

// Node in a doubly linked list. Unlike std::list, nodes are not owned by a container.
// Instead, nodes may join or leave a list freely.
// NOTE: Derived classes have to explicitly declare copy / assignment as default,
//       otherwise the compiler generated version would use the const T& signature,
//       which is deleted.
template <typename T = void, typename policy = disable_t>
struct Node : Tail<std::conditional_t<std::is_same_v<T, void>, Node<T, policy>, T>>,
              Head<std::conditional_t<std::is_same_v<T, void>, Node<T, policy>, T>> {
private:
    using this_t = Node<T, policy>;
    using U = std::conditional_t<std::is_same_v<T, void>, this_t, T>;

public:
    using head_t = Head<U>;
    using tail_t = Tail<U>;
    using head_t::next;
    using tail_t::prev;

    Node() = default;
    Node(const this_t&) = delete;
    this_t& operator=(const this_t&) = delete;

    //! constructed node is inserted after the input node
    Node(after_t, head_t& node) : tail_t(&node), head_t(node.next) {
        node.next = this;
        if (next) {
            next->prev = this;
        }
    }

    //! constructed node is inserted before the input node
    Node(before_t, tail_t& node) : head_t(&node), tail_t(node.prev) {
        node.prev = this;
        if (prev) {
            prev->next = this;
        }
    }

    Node(this_t&& rhs) : tail_t(rhs.prev), head_t(rhs.next) {
        rhs.prev = nullptr;
        rhs.next = nullptr;
        if (prev) {
            prev->next = this;
        }
        if (next) {
            next->prev = this;
        }
    }

    Node& operator=(this_t&& rhs) {
        unlink();
        prev = rhs.prev;
        next = rhs.next;
        rhs.prev = nullptr;
        rhs.next = nullptr;
        if (prev) {
            prev->next = this;
        }
        if (next) {
            next->prev = this;
        }
        return *this;
    }

    template<typename p = policy,
             typename = std::enable_if_t<std::is_same_v<p, before_t> || std::is_same_v<p, after_t>, void>>
    Node(this_t& rhs) : Node(policy{}, rhs) {}

    template<typename p = policy,
             typename = std::enable_if_t<std::is_same_v<p, before_t> || std::is_same_v<p, after_t>, void>>
    this_t& operator=(this_t& rhs) {
        insert(policy{}, rhs);
        return *this;
    }

    void unlink() {
        if (prev) {
            prev->next = next;
        }
        if (next) {
            next->prev = prev;
        }
        prev = nullptr;
        next = nullptr;
    }

    //! this node is unlinked from its list and inserted after the input node
    void insert(after_t, head_t& node) {
        unlink();
        prev = &node;
        next = node.next;
        node.next = this;
        if (next) {
            next->prev = this;
        }
    }

    //! this node is unlinked from its list and inserted before the input node
    void insert(before_t, tail_t& node) {
        unlink();
        next = &node;
        prev = node.prev;
        node.prev = this;
        if (prev) {
            prev->next = this;
        }
    }

    void insert_before(tail_t& node) {insert(before_t{}, node);}
    void insert_after(head_t& node) {insert(after_t{}, node);}

    ~Node() {
        unlink();
    }
};

} // namespace mgb::imperative::python::intrusive_list
