/*
 * MOST NetServices "Light" V3.2.7.0.1796 MultiInstance Patch
 *
 * Copyright (C) 2015 Microchip Technology Germany II GmbH & Co. KG
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * You may also obtain this software under a propriety license from Microchip.
 * Please contact Microchip for further information.
 *
 */

/*!
 * \file
 * \brief Implementation of the doubly linked list.
 *
 * \cond MNS_INTERNAL_DOC
 * \addtogroup G_DL
 * @{
 */

/*------------------------------------------------------------------------------------------------*/
/* Includes                                                                                       */
/*------------------------------------------------------------------------------------------------*/
#include "mns_dl.h"
#include "mns_trace.h"

/*------------------------------------------------------------------------------------------------*/
/* Implementation of class CDlList                                                                */
/*------------------------------------------------------------------------------------------------*/
/*! \brief Constructor of the doubly linked list class.
 *  \param self        Instance pointer
 *  \param mns_inst_id MOST NetServices instance ID
 */
void Dl_Ctor(CDlList *self, uint8_t mns_inst_id)
{
    self->head = NULL;
    self->tail = NULL;
    self->size = 0U;
    self->mns_inst_id = mns_inst_id;
}

/*! \brief Inserts a new node after an arbitrary node.
 *  \param self       Instance pointer
 *  \param node       Reference of the initial node
 *  \param new_node   Reference of the new node are to be inserted
 */
void Dl_InsertAfter(CDlList *self, CDlNode *node, CDlNode *new_node)
{
    TR_ASSERT(self->mns_inst_id, "[DL]", (self->size <= 0xFFFFU));
    new_node->prev = node;
    new_node->next = node->next;
    if(node->next == NULL)                      /* Is initial node last node in list? */
    {
        self->tail = new_node;                  /* Set new node as tail of list */
    }
    else
    {
        node->next->prev = new_node;            /* Adjust follower node */
    }
    node->next = new_node;                      /* Adjust parent node */
    new_node->in_use = true;                    /* Signals that node is part of a list */
    self->size++;                               /* Increment number of nodes */
}

/*! \brief Inserts a new node before an arbitrary node.
 *  \param self       Instance pointer
 *  \param node       Reference of the initial node
 *  \param new_node   Reference of the new node are to be inserted
 */
void Dl_InsertBefore(CDlList *self, CDlNode *node, CDlNode *new_node)
{
    TR_ASSERT(self->mns_inst_id, "[DL]", (self->size <= 0xFFFFU));
    new_node->prev = node->prev;
    new_node->next = node;
    if(node->prev == NULL)                      /* Is initial node first node in list? */
    {
        self->head = new_node;                  /* Set new node as head of list */
    }
    else
    {
        node->prev->next = new_node;            /* Adjust parent node */
    }
    node->prev = new_node;                      /* Adjust follower node */
    new_node->in_use = true;                    /* Signals that node is part of a list */
    self->size++;                               /* Increment number of nodes */
}

/*! \brief Sets the new node as head of a doubly linked list.
 *  \param self       Instance pointer
 *  \param new_node   Reference of the new node are to be placed as head of the list
 */
void Dl_InsertHead(CDlList *self, CDlNode *new_node)
{
    if(self->head == NULL)                      /* Is list empty? */
    {
        TR_ASSERT(self->mns_inst_id, "[DL]", (self->size <= 0xFFFFU));
        self->head = new_node;
        self->tail = new_node;
        new_node->prev = NULL;
        new_node->next = NULL;
        new_node->in_use = true;                /* Signals that node is part of a list */
        self->size++;                           /* Increment number of nodes */
    }
    else
    {
        Dl_InsertBefore(self, self->head, new_node);
    }
}

/*! \brief Inserts the new node at the end of a doubly linked list.
 *  \param self       Instance pointer
 *  \param new_node   Reference of the new node are to be placed at the end of the list
 */
void Dl_InsertTail(CDlList *self, CDlNode *new_node)
{
    if(self->tail == NULL)                      /* Is list empty? */
    {
        Dl_InsertHead(self, new_node);
    }
    else
    {
        Dl_InsertAfter(self, self->tail, new_node);
    }
}

/*! \brief  Removes an arbitrary node from a doubly linked list.
 *  \param  self   Instance pointer
 *  \param  node   Reference of the node are to be removed from the list
 *  \return \c DL_OK: No error
 *  \return \c DL_UNKNOWN_NODE: Given node is not part of this list
 */
Dl_Ret_t Dl_Remove(CDlList *self, CDlNode *node)
{
    Dl_Ret_t ret_val = DL_UNKNOWN_NODE;

    if(Dl_IsNodeInList(self, node) != false)    /* Is node part of list? */
    {
        TR_ASSERT(self->mns_inst_id, "[DL]", (self->size > 0U));
        if(node->prev == NULL)                  /* First node in list? */
        {
            self->head = node->next;            /* Replace head node with next node in list */
        }
        else                                    /* -> Not first node in list  */
        {
            node->prev->next = node->next;      /* Set next pointer of previous node to next node */
        }
        if(node->next == NULL)                  /* Last node in list? */
        {
            self->tail = node->prev;            /* Replace tail node with previous node in list */
        }
        else                                    /* -> Not last node in list */
        {
            node->next->prev = node->prev;      /* Set previous ptr of next node to previous node */
        }
        node->prev = NULL;
        node->next = NULL;
        node->in_use = false;                   /* Signals that node is not part of a list */
        ret_val = DL_OK;
        self->size--;                           /* Decrement number of nodes */
    }

    return ret_val;
}

/*! \brief  Removes the first node in a doubly linked list.
 *  \param  self   Instance pointer
 *  \return The reference of the removed head node or \c NULL if the list is empty.
 */
CDlNode * Dl_PopHead(CDlList *self)
{
    CDlNode *node = self->head; 

    if(NULL != node)                            /* Is list not empty? */
    {
        TR_ASSERT(self->mns_inst_id, "[DL]", (self->size > 0U));
        self->head = node->next;                /* Replace head node with next node in list */
        if(node->next == NULL)                  /* Last node in list? */
        {
            self->tail = NULL;                  /* Replace tail node and set list's tail pointer 
                                                 *  to NULL
                                                 */
        }
        else                                    /* -> Not last node in list */
        {
            node->next->prev = NULL;            /* Set previous pointer of next node to NULL */
        }
        node->prev = NULL;
        node->next = NULL;
        node->in_use = false;                   /* Signals that node is not part of a list */
        self->size--;                           /* Decrement number of nodes */
    }

    return node;
}

/*! \brief  Removes the last node in a doubly linked list.
 *  \param  self   Instance pointer
 *  \return The reference of the removed tail node or \c NULL if the list is empty.
 */
CDlNode * Dl_PopTail(CDlList *self)
{
    CDlNode *node = self->tail;

    if(NULL != node)                            /* Is list not empty? */
    {
        TR_ASSERT(self->mns_inst_id, "[DL]", (self->size > 0U));
        if(node->prev == NULL)                  /* First node in list? */
        {
            self->head = NULL;                  /* Replace head node and set list's head pointer 
                                                 * to NULL
                                                 */
        }
        else                                    /* -> Not first node in list  */
        {
            node->prev->next = NULL;            /* Set next pointer of previous node to NULL */
        }
        self->tail = node->prev;                /* Replace tail node with previous node in list */
        node->prev = NULL;
        node->next = NULL;
        node->in_use = false;                   /* Signals that node is not part of a list */
        self->size--;                           /* Decrement number of nodes */
    }

    return node;
}

/*! \brief  Returns the reference of the first node in a doubly linked list.
 *  \param  self   Instance pointer
 *  \return The reference of the head node or \c NULL if the list is empty.
 */
CDlNode * Dl_PeekHead(CDlList *self)
{
    return self->head;
}

/*! \brief  Returns the reference of the last node in a doubly linked list.
 *  \param  self   Instance pointer
 *  \return The reference of the tail node or NULL if the list is empty.
 */
CDlNode * Dl_PeekTail(CDlList *self)
{
    return self->tail;
}

/*! \brief  Calls the given function for each node in the doubly linked list. If the func_ptr 
 *          returns true the loop is stopped and the current node will be returned.
 *  \param  self           Instance pointer
 *  \param  func_ptr       Reference of the callback function which is called for each node
 *  \param  user_data_ptr  Reference of optional user data given to func_ptr
 *  \return Returns the current node or \c NULL if the whole list is processed.
 */
CDlNode * Dl_Foreach(CDlList *self, Dl_ForeachFunc_t func_ptr, void *user_data_ptr)
{
    CDlNode *ret_val = NULL;
    CDlNode *node = self->head;

    while(NULL != node)                                         /* End of list reached? */
    {
        if(func_ptr(node->data_ptr, user_data_ptr) != false)    /* Data found? */
        {
            ret_val = node;
            break;
        }
        node = node->next;
    }
    return ret_val;
}

/*! \brief  Checks if a node is part of the given doubly linked list.
 *  \param  self   Instance pointer
 *  \param  node   Reference of the searched node
 *  \return \c true: Node is part of the given list
 *  \return \c false: Node is not part of the given list
 */
bool Dl_IsNodeInList(CDlList *self, const CDlNode *node)
{
    bool ret_val = false;
    CDlNode *current_node = self->head;

    while(NULL != current_node)                 /* End of list reached? */
    {
        if(current_node == node)                /* Is current node the searched one */
        {
            ret_val = true;
            break;
        }
        current_node = current_node->next;
    }
    return ret_val;
}

/*! \brief Appends one doubly linked list to another doubly linked list.
 *  \param self       Instance pointer
 *  \param list_ptr   Reference to the doubly linked list
 */
void Dl_AppendList(CDlList *self, CDlList *list_ptr)
{
    TR_ASSERT(self->mns_inst_id, "[DL]", (list_ptr != NULL));
    if(list_ptr->head != NULL)
    {
        if(self->tail == NULL)             /* Is list empty? */
        {
            self->head = list_ptr->head;
            self->tail = list_ptr->tail;
            self->size = list_ptr->size;
        }
        else
        {
            list_ptr->head->prev = self->tail;
            self->tail->next = list_ptr->head;
            self->tail = list_ptr->tail;
            self->size += list_ptr->size;
        }
        list_ptr->head = NULL;
        list_ptr->tail = NULL;
        list_ptr->size = 0U;
    }
}

/*! \brief  Interface function to retrieve the list size.
 *  \param  self   Instance pointer
 *  \return Size of the list
 */
uint16_t Dl_GetSize(CDlList *self)
{
    return self->size;
}


/*------------------------------------------------------------------------------------------------*/
/* Implementation of class CDlNode                                                                */
/*------------------------------------------------------------------------------------------------*/
/*! \brief Constructor of doubly linked list nodes.
 *  \param self        Instance pointer
 *  \param data_ptr    Optional reference to data
 */
void Dln_Ctor(CDlNode *self, void *data_ptr)
{
    self->next = NULL;
    self->prev = NULL;
    self->in_use = false;
    self->data_ptr = data_ptr;
}

/*! \brief Interface function to set the data pointer of the given node.
 *  \param self        Instance pointer
 *  \param data_ptr    Reference of the new data
 */
void Dln_SetData(CDlNode *self, void *data_ptr)
{
    self->data_ptr = data_ptr;
}

/*! \brief Interface function to request the data pointer of the given node.
 *  \param self       Instance pointer
 */
void * Dln_GetData(CDlNode *self)
{
    return self->data_ptr;
}

/*! \brief  Checks if a node is part of a doubly linked list.
 *  \param  self   Instance pointer of the searched node
 *  \return \c true:  Node is part of a list
 *  \return \c false: Node is not part of a list
 */
bool Dln_IsNodePartOfAList(CDlNode *self)
{
    return self->in_use;
}

/*!
 * @}
 * \endcond
 */

/*------------------------------------------------------------------------------------------------*/
/* End of file                                                                                    */
/*------------------------------------------------------------------------------------------------*/