#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_ */