#ifndef FRAMEWORK_CONTAINER_SINGLYLINKEDLIST_H_
#define FRAMEWORK_CONTAINER_SINGLYLINKEDLIST_H_

#include <cstddef>
#include <cstdint>

/**
 * @brief    Linked list data structure,
 *             each entry has a pointer to the next entry (singly)
 * @ingroup container
 */
template<typename T>
class LinkedElement {
public:
    T *value;
    class Iterator {
    public:
        LinkedElement<T> *value = nullptr;
        Iterator() {}

        Iterator(LinkedElement<T> *element) :
                value(element) {
        }

        Iterator& operator++() {
            value = value->getNext();
            return *this;
        }

        Iterator operator++(int) {
            Iterator tmp(*this);
            operator++();
            return tmp;
        }

        bool operator==(Iterator other) {
            return value == other.value;
        }

        bool operator!=(Iterator other) {
            return !(*this == other);
        }
        T *operator->() {
            return value->value;
        }
    };

    LinkedElement(T* setElement, LinkedElement<T>* setNext = nullptr):
           value(setElement), next(setNext) {}

    virtual ~LinkedElement(){}

    virtual LinkedElement* getNext() const {
        return next;
    }

    virtual void setNext(LinkedElement* next) {
        this->next = next;
    }

    virtual void setEnd() {
        this->next = nullptr;
    }

    LinkedElement* begin() {
        return this;
    }
    LinkedElement* end() {
        return nullptr;
    }
private:
    LinkedElement *next;
};

template<typename T>
class SinglyLinkedList {
public:
    using ElementIterator = typename LinkedElement<T>::Iterator;

    SinglyLinkedList() {}

    SinglyLinkedList(ElementIterator start) :
            start(start.value) {}

    SinglyLinkedList(LinkedElement<T>* startElement) :
            start(startElement) {}

    ElementIterator begin() const {
        return ElementIterator::Iterator(start);
    }

    /** Returns iterator to nulltr */
    ElementIterator end() const {
        return ElementIterator::Iterator();
    }

    /**
     * Returns last element in singly linked list.
     * @return
     */
    ElementIterator back() const {
        LinkedElement<T> *element = start;
        while (element->getNext() != nullptr) {
            element = element->getNext();
        }
        return ElementIterator::Iterator(element);
    }

    size_t getSize() const {
        size_t size = 0;
        LinkedElement<T> *element = start;
        while (element != nullptr) {
            size++;
            element = element->getNext();
        }
        return size;
    }
    void setStart(LinkedElement<T>* firstElement) {
        start = firstElement;
    }

    void setNext(LinkedElement<T>* currentElement,
            LinkedElement<T>* nextElement) {
        currentElement->setNext(nextElement);
    }

    void setLast(LinkedElement<T>* lastElement) {
        lastElement->setEnd();
    }

    void insertElement(LinkedElement<T>* element, size_t position) {
        LinkedElement<T> *currentElement = start;
        for(size_t count = 0; count < position; count++) {
            if(currentElement == nullptr) {
                return;
            }
            currentElement = currentElement->getNext();
        }
        LinkedElement<T>* elementAfterCurrent = currentElement->next;
        currentElement->setNext(element);
        if(elementAfterCurrent != nullptr) {
            element->setNext(elementAfterCurrent);
        }
    }

    void insertBack(LinkedElement<T>* lastElement) {
        back().value->setNext(lastElement);
    }

protected:
    LinkedElement<T> *start = nullptr;
};

#endif /* SINGLYLINKEDLIST_H_ */