#ifndef LIST_H
#define LIST_H

template <typename T>
class List
{
public:
    List():_next(NULL), _head(this), _value(NULL)
    {
    }
    ~List()
    {
        if(_head == this)
            dispose();
    }

    boolean addFirst(T value)
    {
        List *listNode = new List(new T(value));
        return addNewNodeAtTheStart(listNode);
    }
    boolean addFirst(T *value)
    {
        List *listNode = new List(value);
        return addNewNodeAtTheStart(listNode);
    }

    boolean addLast(T value)
    {
        List *listNode = new List(new T(value));
        return addNewNodeAtTheEnd(listNode);
    }
    boolean addLast(T *value)
    {
        List *listNode = new List(value);
        return addNewNodeAtTheEnd(listNode);
    }

    T* getRef(unsigned int index)
    {
        unsigned int position(0);
        if(_head->_next == NULL) return NULL;

        List *cursor = _head;

        while(!isListEmpty(cursor->_next))
        {
            if(position++ == index)
            {
                return cursor->_next->_value;
            }
            cursor = cursor->_next;
        }
        return NULL;
    }
    T get(unsigned int index)
    {
        T* p = getRef(index);
        if(p == NULL)
        {
            T value;
            return value;
        }else
        {
            T value(*p);
            return value;
        }
    }

    T* getFirstRef()
    {
        if(_head->_next == NULL) return NULL;

        return _head->_next->_value;
    }
    T getFirst()
    {
        T* p = getFirstRef();
        if(p == NULL)
        {
            T value;
            return value;
        }else
        {
            T value(*p);
            return value;
        }
    }

    T* getLastRef()
    {
        if(_head->_next == NULL) return NULL;

        List *cursor = _head;
        while(!isListEmpty(cursor->_next))
        {
            cursor = cursor->_next;
        }

        return cursor->_value;
    }
    T getLast()
    {
        T* p = getLastRef();
        if(p == NULL)
        {
            T value;
            return value;
        }else
        {
            T value(*p);
            return value;
        }
    }

    T* removeRef(unsigned int index)
    {
        unsigned int position(0);
        if(_head->_next == NULL) return NULL;

        List *cursor = _head, *toRemove(NULL);

        while(!isListEmpty(cursor->_next))
        {
            if(position++ == index || position == _head->count())
            {
                toRemove = cursor->_next;
                cursor->_next = cursor->_next->_next;

                T *toReturn = toRemove->_value;
                delete toRemove;
                return toReturn;
            }
            cursor = cursor->_next;
        }
        return NULL;
    }
    T remove(unsigned int index)
    {
        T *ref = removeRef(index);
        if(ref != NULL)
        {
            T value(*ref);
            delete ref;
            return value;
        }else
        {
            T value;
            return value;
        }
    }

    T* removeFirstRef()
    {
        if(isListEmpty(_head->_next))return NULL;

        T *refToReturn = _head->_next->_value;
        List *toDelete(_head->_next);
        _head->_next = _head->_next->_next;

        delete toDelete;
        return refToReturn;
    }
    T removeFirst()
    {
        T *ref = removeFirstRef();
        if(ref != NULL)
        {
            T value(*ref);
            delete ref;
            return value;
        }else
        {
            T value;
            return value;
        }
    }

    T* removeLastRef()
    {
        unsigned int position(0);
        if(_head->_next == NULL) return NULL;

        List *cursor = _head, *toRemove(NULL);

        while(!isListEmpty(cursor->_next->_next))
        {
            cursor = cursor->_next;
        }
        toRemove = cursor->_next;
        cursor->_next = cursor->_next->_next;

        T *toReturn = toRemove->_value;
        delete toRemove;
        return toReturn;
    }
    T removeLast()
    {
        T *ref = removeLastRef();
        if(ref != NULL)
        {
            T value(*ref);
            delete ref;
            return value;
        }else
        {
            T value;
            return value;
        }
    }
    int contains(T *value)
    {
        unsigned int position(-1);
        if(_head->_next == NULL) return NULL;

        List *cursor = _head;

        while(!isListEmpty(cursor->_next))
        {
            position++;

            if(*(cursor->_next->_value) == *value)
                return position;

            cursor = cursor->_next;
        }
        return -1;
    }
    int contains(T value)
    {
        return contains(new T(value));
    }
    unsigned int count()
    {
        unsigned int counter(0);
        if(isListEmpty(_head->_next))return counter;

        List *cursor = _head->_next;
        while(!isListEmpty(cursor))
        {
            counter++;
            cursor = cursor->_next;
        }
        return counter;
    }
    void clear(){_head->dispose();}
    void dispose()
    {
        if(isListEmpty(_head->_next))return;

        List *cursor = _head->_next, *toDelete(NULL);

        while(!isListEmpty(cursor))
        {
            toDelete = cursor;
            cursor = cursor->_next;

            delete toDelete->_value;
            delete toDelete;
        }
        _head = this;
        _next = NULL;
    }
protected:
    List(T *value):List()
    {
        _value = value;
    }
    boolean isListEmpty(List *node){return node == NULL;}
    boolean addNewNodeAtTheEnd(List *node)
    {
        if(node == NULL) return false;

        node->_head = _head;

        if(_next == NULL)
        {
            _next = node;
            return true;
        }

        // /!\ We have to work with the _next reference in the loop, if we don't it won't work as expected
        List *cursor = _head;
        while(!isListEmpty(cursor->_next))
        {
            cursor = cursor->_next;
        }
        cursor->_next = node;

        return true;
    }

    boolean addNewNodeAtTheStart(List *node)
    {
        if(node == NULL) return false;

        node->_head = _head;

        if(_next == NULL)
        {
            _next = node;
            return true;
        }

        List *temp = _next;
        _next = node;
        node->_next = temp;

        return true;
    }

    List *_next;
    List *_head;
    T* _value;
private:
};

#endif //LIST_H
