// linked.c   ANSI C kernel environment linked list
// requires kmalloc to allocate space for linked list

#include "linked.h"

// Create a blank node using kmalloc, then use allocated memory to store
// a linked list node with the values passed in the parameters
// You must capture the node returned with an lval result variable
node *create_node( u32 base_register, u32 limit_register, bool ft_hole_mem, u32 id)
{
   node *new_node = NULL;

	new_node = (node *) kmalloc( NODE_SIZE, 0, (u32 *) new_node);
	new_node->id = id;
	new_node->base_register = base_register;
	new_node->limit_register = limit_register * (new_node->id + 1);
	new_node->ft_hole_mem = ft_hole_mem;
	new_node->next = NULL;
	new_node->previous = NULL;

   return( new_node);
}

// Creates a node from parameters using the above and then passes it to insert the node into the linked list
// changes to *head will only affect a local stack variable
// You must capture the new head returned with an lval result variable to update head changes
// ft_hole_mem is not used currently but is included to maintain a "holes" list
// true means update memory list,  false means update holes list (if you were to use it)
node *add_node( node *head, u32 base_register, u32 limit_register, bool ft_hole_mem, u32 id)
{
   node *new_node = NULL;

   new_node = create_node( base_register, limit_register, ft_hole_mem, id);
   head = _add_node( head, new_node);
   return( head);
}

// place an already created node at the end of the list
// changes to *head will only affect a local stack variable
// You must capture the new head returned with an lval result variable to update head changes
node *_add_node( node *head, node *new_node)
{
	node *iterator = NULL;
   node *previous = NULL;

   if( new_node == NULL) {
      return(head);
   }
	if( head == NULL) {
		head = new_node;
		return ( head );
	}  // Stop here is head is empty and just set to new and return new head.

	iterator = head->next;   // Head is not NULL so continue with next node as iterator
	while( iterator != NULL) {
      previous = iterator;
		iterator = iterator->next;
	} // until iterator is null keep pointing new node back to previous node and iterator to next node

	if( previous == NULL) { // just head existed, append new node ...
		new_node->previous = head;
      head->next = new_node;
	} else {    // iterated to end of list, append new node ...
      new_node->previous = previous;
      previous->next = new_node;
   } // ... and return
	return( head);
}

// Iterate to last node and list and then append new node to it
node *append_node( node *head, node* new_node)
{
   node *iterator = head;

   if( new_node == NULL) {
      return( head);
   }
   if( head == NULL) {
      return( new_node);
   }
   while( iterator->next != NULL) {  // step until at last node
      iterator = iterator->next;
   }
   iterator->next = new_node;
   new_node->previous = iterator;
   new_node->next = NULL;
   return(head);
}
      
// Given an input id search list until id found
// return lval of that *node value otherwise return a NULL value
node *find_id( node *head, u32 id)
{
   if( head == NULL) {
      return( head);
   }
   node *iterator = head;

   while(( iterator != NULL) && ( iterator->id != id)) {  // search and compare id
      iterator = iterator->next;
   }
   return( iterator);
}
 
// Given an id, find it with the function above and then use node* value (target)
// to create a gap of previous and next nodes which is then closed.
// if id is not found nothing happens
node *remove_node_by_id( node *head, u32 id)
{
   node *target = NULL;

   target = find_id( head, id);
   if( target == NULL) {
      return( head);
   }
   head = zip_list( head, target);
   free_node( target);
   return( head);
}

// Given a specific lval node* target value, remove it from the list *head
// and then close the gap created by removing target.
node *zip_list( node *head, node* target)
{
   bool has_previous = true;
   bool has_next = true;

   if( target == NULL) {
      return( head);
   }
   if( target->previous == NULL) {
      has_previous = false; 
#ifdef KDEBUG
      kprint("has_previous FALSE\n");
#endif
   }
   if ( target->next == NULL) {
      has_next = false;
#ifdef KDEBUG
      kprint("has_next FALSE\n");
#endif
   }
   if( (has_next == true) && (has_previous == true)) {      // Node in middle of chain
      target->previous->next = target->next;
      target->next->previous = target->previous;
   } else if ( (has_next == false && has_previous == true)) {  // Node at end of chain
      target->previous->next = NULL;
   } else if ( (has_next == true && has_previous == false)) {  // Node at start of chain
      target->next->previous = NULL;
      head = head->next;
   } else { // ( has_next == false && has_previous == false)    // Node is isolated
      head = NULL;
   }
   return(head);
}

// Use find_id to get the insert_point
// decide if insertion is before or after
// if ft_before_after = FALSE then insert before target
// if ft_before_after = TRUE then append after target
node *insert_node( node *head, node* insert_point, node *new_node, bool ft_before_after)
{
   if( new_node == NULL) {  // Nothing to add so return unchanged
      return( head);
   }
   if( insert_point == NULL) {  // If no point specified either place before head or after tail
      if( ft_before_after) {    // if after is set append as new tail
         head = append_node( head, new_node);
      } else {                   // if before than insert before head as new head
         new_node->next = head;
         new_node->previous = NULL;
         head-> previous = new_node;
         return( new_node);      // return new head
      }
      return( head);
   }

   if( ft_before_after) {   // if adding after then...
      if( insert_point->next != NULL) {  // insert node after insertion_point
         insert_point->next->previous = new_node;
         new_node->next = insert_point->next;
      } else {  // insertion point is last node instead, skip above operations
         new_node->next = NULL;
      }
      new_node->previous = insert_point;
      insert_point->next = new_node;
   } else {
      if( insert_point->previous != NULL) { // insert node before insertion point
         insert_point->previous->next = new_node;
         new_node->previous = insert_point->previous;
      } else { // insertion point is head
         new_node->previous = NULL;
         head = new_node;   // set new head as insertion point
      }
      insert_point->previous = new_node;
      new_node->next = insert_point;
   }
   return(head);  // return changes (if any) to head
}

node *get_tail( node *head)
{
   if( head == NULL) {
      return(head);
   }
   node *tail = head;
   while( tail->next != NULL) {  // iterate until the next node is not NULL
      tail = tail->next;
   }
   return( tail);
}

void get_min_max_id( node *head, u32 *min, u32 *max)
{
   if( head == NULL) {
      return;
   }
   node *iterator = head;
   *min = 0xFFFFFFFF;
   *max = 0;

   while( iterator != NULL) {  // iterate until the next node is not NULL
      if( *min > iterator->id) {
         *min = iterator->id;
      }
      if( *max < iterator->id) {
         *max = iterator->id;
      }
      iterator = iterator->next;
   }
   return;
}

void swap_node_data( node* left, node *right)
{
	if(( left == NULL) || (right == NULL)) {
		kprint("Invalid element.\n");
		return;
	}
   node swap;

   kprint_hex("SWAP ", left->id, 4);
   kprint_hex(" with ", right->id, 4);
   kprint("\n");

   swap.id = left->id;
   swap.base_register = left->base_register;
   swap.limit_register = left->limit_register;
   swap.ft_hole_mem = left->ft_hole_mem;

   left->id = right->id;
   left->base_register = right->base_register;
   left->limit_register = right->limit_register;
   left->ft_hole_mem = right->ft_hole_mem;

   right->id = swap.id;
   right->base_register = swap.base_register;
   right->limit_register = swap.limit_register;
   right->ft_hole_mem = swap.ft_hole_mem;

   return;
}

node *swap_nodes( node *head, node** left, node **right)
{

   if( head == NULL) {   // don't swap nodes in a list that is empty
      return( head);
   }

   if( (*left == NULL) || (*right == NULL) ) {   // if either or both nodes are NULL do nothing
      return( head);
   }
   if ( head == *left) {      // if left happens to be head than set head to right
      head = *right;
   }
   if ( head == *right) {     // if right happens to be head then set head to left
      head = *left;
   }
      
   node *left_previous = (*left)->previous;
   node *left_next = (*left)->next;
   node *right_previous = (*right)->previous;
   node *right_next = (*right)->next;

   if( (*left)->previous != NULL) {    // make sure not to access a NULL value node
      (*left)->previous->next = *right;    // previous node to left now points to right instead of left
   }

   if( (*left)->next != NULL) {     // same
      (*left)->next->previous = *right;   // next node to left now points to right instead of left
   }

   if( (*right)->previous != NULL) {   // same
      (*right)->previous->next = *left;    // same as above but right previous to left now
   }
   if( (*right)->next != NULL) {       // same
      (*right)->next->previous = *left;    // same as above...
   }

   (*left)->previous = right_previous; // this will obliterate left->previous so it is preserved in left_previous
   (*left)->next = right_next;         // this will obliterate left->next so it is preserved in left_next

   (*right)->previous = left_previous; // now point right node back to previous to left node (obliterated above)
   (*right)->next = left_next;         // now point right node forward to next of left node (obliterated above)

	node* swap;
	swap = *left;
	*left = *right;
	*right = swap;

#ifdef KDEBUG
	kprint("LEFT: ");
	print_node( *left);
	kprint("     RIGHT: ");
	print_node( *right);
	kprint("\n");
#endif

   return( head);    // return new head if it changed
}

// good ways to sort a list, transfer it to an array and merge or quicksort it
// not so good bubble-sort, insertion, select-sort, heap-sort
// worst, this ugly hack
node *hacksort_list( node* head, bool ft_descending_ascending)
{
   if( head == NULL) {
      kprint("LIST EMPTY\n");
      return(head);
   }
   if( ft_descending_ascending) {
      kprint("ASCENDING\n");
   } else {
      kprint("DESCENDING\n");
   }
   node* outer_iterator = head;
   node* inner_iterator = head->next;
   node* max = head;

   while( outer_iterator != NULL) {
      max = outer_iterator;
      inner_iterator = outer_iterator->next;
      while( inner_iterator != NULL) {
         if( ft_descending_ascending) {
            if( max->id > inner_iterator->id) {    // This decides ASCENDING
               max = inner_iterator;
            }
         } else {
            if( max->id < inner_iterator->id) {   // This decides SORT DESCENDING
               max = inner_iterator;
            }
         }
         inner_iterator = inner_iterator->next;
/*******  STOP COUNTER for Infnite Loop Halting
   u32 count = 0;
 if( count++ > 10) {
   return(head);
 }
****/
      }
      if( ( outer_iterator != NULL) && ( outer_iterator != max)) {
         swap_node_data( outer_iterator, max);
      }
      outer_iterator = outer_iterator->next;
   }
   return( head);
}
   
node *mergesort_list( node* head, node *pivot, node *left, node *right)
{
   return( head);  // disabled, not yet implemented
   if( ( head == NULL) || ( pivot == NULL) | ( left == NULL) | ( right == NULL)) {
      return( head);
   }
   node *start = left;
   node *end = right;
   while( (start->next != NULL) || (start->id != end->id)) {
      if( start->id < pivot->id) {
         ;
      }
   }
   
   return( head);
}

// stand in for delete, this only zeroes all the values of the node allocated by kmalloc
// the memory is not reclaimed, need to add a tracker for these kmalloc allocated blocks
// to implement a proper delete function
void free_node( node *target)
{
   memory_set( (u8 *) target, 0, NODE_SIZE);
   target = NULL;
   return;
}

// Print a single node's values
void print_node( node *current)
{ 
   char numstr[16];

	if( current->previous != NULL) {
		kprint_hex( "(", current->previous->id, 4);
		kprint( ")<-");
	} else {
		kprint("NULL<-");
	}

   memory_set( (u8 *) numstr, 0, 16);
   hex_to_ascii( current->id, numstr, 16);
   kprint("ID: "); kprint( numstr); kprint(" --- ");
   memory_set( (u8 *) numstr, 0, 16);
   hex_to_ascii( current->base_register, numstr, 16);
   kprint("BASE: "); kprint( numstr); kprint(" --- ");
   memory_set( (u8 *) numstr, 0, 16);
   hex_to_ascii( current->limit_register, numstr, 16);
   kprint("LIMIT: "); kprint( numstr); kprint(" --- ");
   if( current->ft_hole_mem) {
      kprint("MEMORY ");
   } else {
      kprint("HOLE  ");
   }
	if( current->next != NULL) {
		kprint_hex( "->", current->next->id, 4);
	} else {
		kprint("->NULL");
	}
	kprint("\n");
   return;
}

// just print the id separated by commas with a line-feed at the end
void shortprint_list( node *head, bool ft_descending_ascending) 
{
   if( head == NULL) {
      kprint( "EMPTY.");
      kprint("\n");
      return;
   }
   node *iterator = NULL;
   if( ft_descending_ascending) {
      iterator = head;
      while( iterator != NULL) {
         kprint_hex( ",",iterator->id, 4);
         iterator = iterator->next;
      }
   } else {
      iterator = get_tail( head);
      while( iterator != NULL) {
         kprint_hex( ",",iterator->id, 4);
         iterator = iterator->previous;
      }
   }
   kprint("\n");
   return;
}

// iterate through a list printing out the values of each node
void print_list( node *head, bool ft_descending_ascending)
{
   if( head == NULL) {
      kprint( "EMPTY.");
      return;
   }
   node *iterator = NULL;
   if( ft_descending_ascending) {
      iterator = head;
      while( iterator != NULL) {
         print_node( iterator);
         iterator = iterator->next;
      }
   } else {
      iterator = get_tail( head);
      while( iterator != NULL) {
         print_node( iterator);
         iterator = iterator->previous;
      }
   }
   return;
}
