/**\file n_list.c
 *  Pointer list functions definitions
 *\author Castagnier Mickaël
 *\version 2.0
 *\date 24/04/13
 */

#include "n_list.h"



/*!\fn LIST *new_generic_list( int max_items )
 *\brief Initialiaze a generic list container to max_items pointers.
 *\param max_items Specify a max size for the list container, 0 or negative for unlimited lists.
 *\return a new LIST or NULL
 */
LIST *new_generic_list( int max_items )
{
   LIST *list = NULL ;

   Malloc( list , LIST , 1 ); 
   _assert( list , return FALSE );

   if( max_items <= 0 )
      list -> nb_max_items = 0 ;
   else
      list -> nb_max_items = max_items ;

   list -> nb_items = 0 ; 
   list -> start = list -> end = NULL ;

   return list ;
} /* new_generic_list */



/*!\fn LIST_NODE *new_list_node( void *ptr , void (*destructor)( void *ptr ) )
 *\brief Allocate a new node to link in a list.
 *\param ptr The pointer you want to put in the node.
 *\param destructor Pointer to the ptr type destructor function. Leave to NULL if there isn't.
 *\return A new node or NULL on error. Check error messages in DEBUG mode.
 */
LIST_NODE *new_list_node( void *ptr , void (*destructor)( void *ptr ) )
{
   LIST_NODE *node = NULL ;

   Malloc( node , LIST_NODE , 1 ); 
   _assert( node ,  n_log( LOG_ERR , "Error allocating node for ptr %p" , ptr ); return NULL );

   node -> ptr = ptr ;
   node -> destroy_func = destructor ;
   node -> next = node -> prev = NULL ;

   return node ;
} /* new_list_node(...) */ 



/*!\fn void *remove_list_node_f( LIST *list , LIST_NODE *node )
 *\brief Internal function called each time we need to get a node out of a list
 *\param list The list to pick in
 *\param node The node to remove in the list
 *\return The node or NULL
 */
void *remove_list_node_f( LIST *list , LIST_NODE *node )
{
   void *ptr = NULL ;

   _assert( list        , n_log( LOG_ERR , "can't remove from NULL list" );        return NULL );
   _assert( list->start , n_log( LOG_ERR , "can't remove from NULL list->start" ); return NULL );
   _assert( list->end   , n_log( LOG_ERR , "can't remove from NULL list->end" );   return NULL );
   _assert( node        , n_log( LOG_ERR , "can't remove from NULL node" );        return NULL );

   ptr = node -> ptr ;
   if( node -> prev && node -> next )
   {
      node -> prev -> next = node -> next ;
      node -> next -> prev = node -> prev ;
   }
   else
   {
      if( node -> prev == NULL && node -> next )
      {
         node -> next -> prev = NULL ;
         list -> start = node -> next ;
      }
      else
      {
         if( node -> prev && node -> next == NULL )
         {
            node -> prev -> next = NULL ;
            list -> end = node -> prev ;
         }
         else
         {
            if( node -> prev == NULL && node -> next == NULL )
            {
               /* removing last item */
               list -> start = list -> end = NULL ;
            }
         }
      }
   }
   Free( node );
   list -> nb_items -- ;
   return ptr ;
} /* remove_list_node_f(...) */



/*!\fn int list_push( LIST *list , void *ptr , void (*destructor)( void *ptr ) )
 *\brief Add a pointer to the end of the list. 
 *\param list An initilized list container. A null value will cause an error and a _log message.
 *\param ptr The pointer you wan to put in the list. A null value will cause an error and a _log message.
 *\param destructor Pointer to the ptr type destructor function. Leave to NULL if there isn't.
 *\return TRUE or FALSE. Check error messages in DEBUG mode.
 */
int list_push( LIST *list , void *ptr , void (*destructor)( void *ptr ) ) 
{
   LIST_NODE *node = NULL ;

   _assert( list , n_log( LOG_ERR , "invalid list: NULL" ); return FALSE );
   _assert( ptr  , n_log( LOG_ERR , "invalid ptr: NULL" );  return FALSE );

   if( list -> nb_max_items > 0 && ( list -> nb_items >= list -> nb_max_items ) )
   {
      n_log( LOG_ERR , "list is full" );
      return FALSE ;
   }

   node = new_list_node( ptr , destructor );
   _assert( node , n_log( LOG_ERR , "Couldn't allocate new node" ); return FALSE );

   list -> nb_items ++ ;

   if( list -> end )
   {
      list -> end -> next = node ;
      node -> prev = list -> end ;
      list -> end = node ;               
   }
   else
   {
      list -> start = list -> end = node ;      
   }   
   return TRUE ;
} /* list_push( ... ) */



/*!\fn int list_push_sorted( LIST *list , void *ptr , int (*comparator)( const void *a , const void *b ) , void (*destructor)( void *ptr ) )
 *\brief Add a pointer sorted in the list , starting by the end of the list.
 *\param list An initilized list container. A null value will cause an error and a _log message.
 *\param ptr The pointer you wan to put in the list. A null value will cause an error and a _log message.
 *\param comparator A pointer to a function which take two void * pointers and return an int.
 *\param destructor Pointer to the ptr type destructor function. Leave to NULL if there isn't.
 *\return TRUE or FALSE. Check error messages in DEBUG mode.
 */
int list_push_sorted( LIST *list , void *ptr , int (*comparator)( const void *a , const void *b ) , void (*destructor)( void *ptr ) )
{
   LIST_NODE *nodeptr = NULL ;

   _assert( list , n_log( LOG_ERR , "invalid list: NULL" ); return FALSE );
   _assert( ptr  , n_log( LOG_ERR , "invalid ptr: NULL" );  return FALSE ); 

   if( list -> nb_max_items > 0 && ( list -> nb_items >= list -> nb_max_items ) )
   {
      n_log( LOG_ERR , "list is full" );
      return FALSE ;
   }

   if( list -> end )
   {
      nodeptr = list -> end ;
      while( nodeptr && ( comparator( ptr , nodeptr -> ptr ) < 0 ) )
         nodeptr = nodeptr -> prev ;

      if( !nodeptr )
      {
         /* It's the lower ranked element in the sort */
         list_unshift( list , ptr , destructor );
      }   
      else
      {
         /* we have a match inside the list. let's insert the datas */
         LIST_NODE *node_next = nodeptr -> next  ;
         LIST_NODE *newnode = new_list_node( ptr , destructor );
         _assert( newnode , n_log( LOG_ERR , "Couldn't allocate new node" ); return FALSE );

         if( node_next )
         {
            link_node( newnode , node_next );
         }   
         else 
            list -> end = newnode ;

         link_node( nodeptr , newnode );
         list -> nb_items ++ ;
      }
   }
   else
      list_push( list , ptr , destructor );

   return TRUE ;
} /* list_push_sorted( ... ) */



/*!\fn int list_unshift( LIST *list , void *ptr , void (*destructor)( void *ptr ) )
 *\brief Add a pointer at the start of the list.
 *\param list An initilized list container. A null value will cause an error and a _log message.
 *\param ptr The pointer you wan to put in the list. A null value will cause an error and a _log message.
 *\param destructor Pointer to the ptr type destructor function. Leave to NULL if there isn't.
 *\return A void *ptr or NULL. Check error messages in DEBUG mode.
 */
int list_unshift( LIST *list , void *ptr , void (*destructor)( void *ptr ) )
{
   LIST_NODE *node = NULL ;

   _assert( list , n_log( LOG_ERR , "invalid list: NULL" ); return FALSE );
   _assert( ptr  , n_log( LOG_ERR , "invalid ptr: NULL" );  return FALSE );

   if( list -> nb_max_items > 0 && ( list -> nb_items >= list -> nb_max_items ) )
   {
      n_log( LOG_ERR , "list is full" );
      return FALSE ;
   }
   node = new_list_node( ptr , destructor );
   _assert( node , n_log( LOG_ERR , "Couldn't allocate new node" ); return FALSE );

   list -> nb_items ++ ;

   if( list -> start )
   {
      link_node( node , list -> start );
      list -> start = node ;               
   }
   else
   {
      list -> start = list -> end = node ;      
   }

   return TRUE ;
} /* list_unshift_f(...) */



/*!\fn int list_unshift_sorted( LIST *list , void *ptr , int (*comparator)( void *a , void *b ) , void (*destructor)( void *ptr )  )
 *\brief Add a pointer sorted in the list , starting by the start of the list.
 *\param list An initilized list container. A null value will cause an error and a _log message.
 *\param ptr The pointer you wan to put in the list. A null value will cause an error and a _log message.
 *\param comparator A pointer to a function which take two void * pointers and return an int.
 *\param destructor Pointer to the ptr type destructor function. Leave to NULL if there isn't.
 *\return TRUE or FALSE. Check error messages in DEBUG mode.
 */
int list_unshift_sorted( LIST *list , void *ptr , int (*comparator)( void *a , void *b ) , void (*destructor)( void *ptr ) )
{
   LIST_NODE *nodeptr = NULL ;

   _assert( list , n_log( LOG_ERR , "invalid list: NULL" ); return FALSE ); 
   _assert( ptr  , n_log( LOG_ERR , "invalid ptr: NULL" );  return FALSE ); 

   if( list -> nb_max_items > 0 && ( list -> nb_items >= list -> nb_max_items ) )
   {
      n_log( LOG_ERR , "list is full" );
      return FALSE ;
   }

   if( list -> start )
   {
      nodeptr = list -> start ;
      while( nodeptr && ( comparator( ptr , nodeptr -> ptr ) > 0 ) )
         nodeptr = nodeptr -> next ;

      if( !nodeptr )
      {
         /* It's the higher ranked element in the sort */
         list_push( list , ptr , destructor );
      }   
      else
      {
         /* we have a match inside the list. let's insert the datas */
         LIST_NODE *node_prev = nodeptr -> prev  ;
         LIST_NODE *newnode = new_list_node( ptr , destructor );
         _assert( newnode , n_log( LOG_ERR , "Couldn't allocate new node" ); return FALSE );

         if( node_prev )
         {   
            link_node( node_prev , newnode );
         }   
         else 
            list -> start = newnode ;

         link_node( newnode , nodeptr );
         list -> nb_items ++ ;
      }
   }
   else
      list_unshift( list , ptr , destructor );

   return TRUE ;
} /* list_unshift_sorted(...) */



/*!\fn void *list_pop_f( LIST *list ) 
 *\brief Get a pointer from the end of the list
 *\param list An initilized list container. A null value will cause an error and a _log message.
 *\return A void *ptr or NULL. Check error messages in DEBUG mode.
 */
void *list_pop_f( LIST *list ) 
{   
   LIST_NODE *nodeptr = NULL ;
   void *ptr = NULL ;

   _assert( list , n_log( LOG_ERR , "invalid list: NULL" ); return NULL );

   if( list -> nb_items == 0 || list -> end == NULL )
      return NULL ;

   nodeptr = list -> end ;
   ptr = nodeptr -> ptr ;

   if( list -> end -> prev )
   {
      list -> end = list -> end -> prev ;
      list -> end -> next = NULL ;
   }      

   list -> nb_items -- ;
   if( list -> nb_items == 0 )
      list -> start = list -> end = NULL ;

   Free( nodeptr );

   return ptr ;
} /* list_pop_f( ... ) */



/*!\fn void *list_shift_f( LIST *list )
 *\brief Get a pointer from the start of the list.
 *\param list An initilized list container. A null value will cause an error and a _log message.
 *\return A void *ptr or NULL. Check error messages in DEBUG mode.
 */
void *list_shift_f( LIST *list )
{
   LIST_NODE *nodeptr = NULL ;
   void *ptr = NULL ;

   _assert( list , n_log( LOG_ERR , "invalid list: NULL" ); return NULL );

   if( list -> nb_items == 0 || list -> start == NULL )
      return NULL ;

   nodeptr = list -> start ;
   ptr = nodeptr -> ptr ;

   if( list -> start -> next )
   {
      list -> start = list -> start -> next ;
      list -> start -> prev = NULL ;
   }      

   list -> nb_items -- ;

   if( list -> nb_items == 0 )
      list -> start = list -> end = NULL ;

   Free( nodeptr );

   return ptr ;
} /* list_shift_f(...)*/



/*!\fn int list_empty( LIST *list )
 *\brief Empty a LIST list of pointers.
 *\param list The list to empty. An already empty or null list will cause an error and a _log message.
 *\return TRUE or FALSE. Check error messages in DEBUG mode.
 */
int list_empty( LIST *list )
{
   LIST_NODE *node = NULL , *node_ptr = NULL ;

   _assert( list , n_log( LOG_ERR , "list is NULL" ); return FALSE );

   node = list -> start ;
   while( node )
   {
      node_ptr = node ;
      node = node -> next ;
      if( node_ptr -> destroy_func != NULL )
      {
         node_ptr -> destroy_func( node_ptr -> ptr );
         Free( node_ptr );
      }
      else
      {
         Free( node_ptr );
      }
   }
   list -> start = list -> end = NULL ;
   list -> nb_items = 0 ;
   return TRUE ;
} /* list_empty( ... ) */



/*!\fn int list_empty_with_f( LIST *list , void (*free_fnct)( void *ptr ))
 *\brief Empty a LIST list of pointers.
 *\param list The list to empty. An already empty or null list will cause an error and a _log message.
 *\param free_fnct a function ptr to a void func( void *ptr )
 *\return TRUE or FALSE. Check error messages in DEBUG mode.
 */
int list_empty_with_f( LIST *list ,  void (*free_fnct)( void *ptr )  )
{
   LIST_NODE *node = NULL , *node_ptr = NULL ;
   
   if( !list ){ n_log( LOG_ERR , "list is NULL" ); return FALSE ; }
  	
   node = list -> start ;
   while( node )
   {
      node_ptr = node ;
      node = node -> next ;
	  free_fnct( node_ptr -> ptr );
	  Free( node_ptr );
   }
   list -> start = list -> end = NULL ;
   list -> nb_items = 0 ;
   return TRUE ;
} /* list_empty_with_f */



/*!\fn int list_destroy( LIST **list )
 *\brief Empty and Free a list container. Call the destructor each time.
 *\param list The list to destroy. An already empty or null list will cause an error and a _log message.
 *\return TRUE or FALSE. Check error messages in DEBUG mode.
 */
int list_destroy( LIST **list )
{
   _assert( (*list) , n_log( LOG_ERR , "list already destroyed" ); return FALSE );
   list_empty( (*list) );
   Free( (*list ) );
   return TRUE ;
} /* free_list( ... ) */
