/***************************************************************************
*                                                                          *
*  Copyright (C) Golaem S.A.  All Rights Reserved.                         *
*                                                                          *
***************************************************************************/

#pragma once

#include "glmAssert.h"

namespace glm
{
template <typename T>
class DLinkedList
{
  public:
    struct Node
    {
        T _data;
        Node* _prev;
        Node* _next;
    };

    DLinkedList()
        : _head(NULL)
    {
    }
    ~DLinkedList()
    {
    }

    bool initializeFromArray(void* buffer, const size_t bufferSize, const size_t elementCount);
    void empty()
    {
        _head = NULL;
    }

    void pushFront(Node* toPush);
    Node* popFront();
    void remove(Node* toRemove);

    bool isEmpty() const
    {
        return (_head == NULL);
    }
    const Node* getHead() const
    {
        return _head;
    }

    static size_t getBufferSizeToAllocate(size_t elementCount)
    {
        return sizeof(Node) * elementCount;
    }

  private:
    Node* _head;
};

template <typename T>
bool DLinkedList<T>::initializeFromArray(void* buffer, const size_t bufferSize, const size_t elementCount)
{
    GLM_ASSERT(elementCount > 0);
    GLM_ASSERT(bufferSize == getBufferSizeToAllocate(elementCount));

    typename DLinkedList<T>::Node* elements = static_cast<typename DLinkedList<T>::Node*>(buffer);

    elements[0]._prev = NULL;

    typename DLinkedList<T>::Node* prevElem = &elements[0];
    for (size_t elemIndex = 1; elemIndex < elementCount; ++elemIndex)
    {
        typename DLinkedList<T>::Node& elem = elements[elemIndex];
        elem._prev = prevElem;
        prevElem->_next = &elem;
        prevElem = &elem;
    }

    elements[elementCount - 1]._next = NULL;

    _head = elements;

    return true;
}

template <typename T>
void DLinkedList<T>::pushFront(typename DLinkedList<T>::Node* toPush)
{
    GLM_ASSERT(toPush->_prev == NULL);
    GLM_ASSERT(toPush->_next == NULL);

    typename DLinkedList<T>::Node*& head = _head;
    GLM_ASSERT(head == NULL || head->_prev == NULL);
    GLM_ASSERT(head == NULL || head->_next == NULL || head->_next->_prev == head);

    toPush->_next = head;
    if (head != NULL)
    {
        head->_prev = toPush;
    }

    head = toPush;

    GLM_ASSERT(head->_prev == NULL);
    GLM_ASSERT(head->_next == NULL || head->_next->_prev == head);
}

template <typename T>
typename DLinkedList<T>::Node* DLinkedList<T>::popFront()
{
    typename DLinkedList<T>::Node*& head = _head;
    GLM_ASSERT(head == NULL || head->_prev == NULL);
    GLM_ASSERT(head == NULL || head->_next == NULL || head->_next->_prev == head);

    typename DLinkedList<T>::Node* popped = NULL;

    if (head != NULL)
    {
        popped = head;
        head = popped->_next;
        popped->_next = NULL;

        if (head != NULL)
        {
            head->_prev = NULL;
        }
    }

    GLM_ASSERT(head == NULL || head->_prev == NULL);
    GLM_ASSERT(head == NULL || head->_next == NULL || head->_next->_prev == head);
    GLM_ASSERT(popped == NULL || (popped->_prev == NULL && popped->_next == NULL));

    return popped;
}

template <typename T>
void DLinkedList<T>::remove(typename DLinkedList<T>::Node* toRemove)
{
    GLM_ASSERT(toRemove != NULL);

    typename DLinkedList<T>::Node*& head = _head;
    GLM_ASSERT(head == NULL || head->_prev == NULL);
    GLM_ASSERT(head == NULL || head->_next == NULL || head->_next->_prev == head);

    //NOTE: not checking that 'toPop' is part of 'list'
    if (toRemove->_prev != NULL)
    {
        toRemove->_prev->_next = toRemove->_next;
    }
    else
    {
        GLM_ASSERT(head == toRemove);
        head = toRemove->_next;
    }

    if (toRemove->_next != NULL)
    {
        toRemove->_next->_prev = toRemove->_prev;
    }

    toRemove->_prev = NULL;
    toRemove->_next = NULL;

    GLM_ASSERT(head == NULL || head->_prev == NULL);
    GLM_ASSERT(head == NULL || head->_next == NULL || head->_next->_prev == head);
}

} // namespace glm
