/*----
  implementation file for my collection of linked list functions
      that use my Node class

  by: Sharon Tuttle
  last modified: 2022-11-01 - adding search_for, insert_after, 
                              delete_node
                 2022-10-27 - started with insert_at_front,
                              print_list, and delete_list
----*/

#include <cstdlib>
#include <iostream>
#include <string>
#include <cmath>
#include "Node.h"
#include "linked-list-functs.h"
using namespace std;

/*----
  signature: insert_at_front: Node*& NodeDataType -> void

  purpose: expects a head pointer to a linked list that is
      PASSED BY REFERENCE, and a piece of data we want to "add"
      to the front of this list, has the side-effects of:
      *   creating a new Node with this data
      *   adding this new node to the FRONT of this list,
          CHANGING the address in the head pointer ARGUMENT
      ...and returns nothing
  
  tests: 
      for:
      Node *start_here = NULL;

      insert_at_front(start_here, 47);

      afterwards:
      *   start_here should point to a Node containing 47
          and that Node should have a next data field of NULL

      if I THEN:
      insert_at_front(start_here, 36);

      afterwards:
      *   start_here should point to a Node containing 36
          and that Node should point to the Node containing 36
          and the Node containing 36 should have a next data field
          of NULL

----*/

void insert_at_front(Node*& head_ptr, NodeDataType new_data)
{
    // need a new Node to hold the new element

    Node *new_node_ptr;

    new_node_ptr = new Node(new_data);

    // to add this new node to the front of the list,
    //    I have 2 cases: is the list currently
    //    empty, or not?

    // I'm handling the empty case first

    if (head_ptr == NULL)
    {
        head_ptr = new_node_ptr;
    }

    // and if the list was not empty, handle that here

    else
    {
        // make the next data field of the new node
        //    point to the CURRENT first node

        new_node_ptr->set_next(head_ptr);

        // NOW it is OK to change the head_ptr to
        //    to the new node

        head_ptr = new_node_ptr;
    }
}

/*---
    signature: print_list: Node* -> void
    purpose: expects a pointer to the beginning of a linked
        list, has the side effect of printing the data in
        each node to the screen on its own line, and returns 
        nothing
    tests:
        for:
        Node *my_first;
        my_first = NULL;

        print_list(my_first);
        ...prints to the screen:

List contents:
==============
==============

        if I then:
        insert_at_front(my_first, 12);
        insert_at_front(my_first, 700);
        insert_at_front(my_first, 15);

        print_list(my_first);
        ...prints to the screen:

List contents:
==============
15
700
12
==============

---*/

void print_list(Node* head_ptr)
{
    cout << "List contents:" << endl;
    cout << "==============" << endl;

    // set up a pointer to help me "walk through" the list

    Node *curr_node_ptr;

    // start it where the current head_ptr is

    curr_node_ptr = head_ptr;

    while (curr_node_ptr != NULL)
    {
        cout << curr_node_ptr->get_data() << endl;

        curr_node_ptr = curr_node_ptr->get_next();
    }

    cout << "==============" << endl;
}

/*---
  signature: delete_list: Node*& -> int
  purpose: expects a pointer to a dynamic linked list
      PASSED BY REFERENCE, has the side-effect of
      freeing those list nodes and setting the argument pointer
      to NULL, and returns the number of list nodes freed.

  tests: if I have:
     Node *start_test = NULL;

     delete_list(start_test) == 0

     if I have:
     insert_at_front(start_test, 400);
     insert_at_front(start_test, 300);
     insert_at_front(start_test, 200);
     insert_at_front(start_test, 100);

     delete_list(start_test) == 4

---*/

int delete_list(Node*& head_ptr)
{
    Node *curr_ptr;
    int num_freed = 0;

    curr_ptr = head_ptr;

    while (curr_ptr != NULL)
    {
        head_ptr = head_ptr->get_next();
        delete curr_ptr;
        curr_ptr = head_ptr;
        num_freed++;
    }

    return num_freed;
}

/*----
  signature: search_for: Node* NodeDataType -> Node*
  purpose: (this is one of MANY possible kinds of search
      functions for searching for something in a linked list!!)
      expects a pointer to the beginning of a linked list
      and a desired element value to search for in that list,
      and returns a pointer to the FIRST node in that list
      that contains this element

      (so -- if the element value is NOT in the list,
      it will return NULL)

  tests:
    for:
    Node *stuff = NULL;
    insert_at_front(stuff, 300);
    insert_at_front(stuff, 400);
    insert_at_front(stuff, 250);
    insert_at_front(stuff, 400);
    insert_at_front(stuff, 25);

    search_for(stuff,  10) == NULL
    search_for(stuff, 25) == stuff
    search_for(stuff, 400) == stuff->get_next()
    
---*/

Node* search_for(Node *start_ptr, NodeDataType desired_element)
{
    bool found = false;

    Node* curr;
    curr = start_ptr;

    while ((curr != NULL) && (found == false))
    {
        //cout << "IN SEARCH LOOP - curr->get_data(): "
        //     << curr->get_data() << endl;
             
        if (curr->get_data() == desired_element)
        {
            found = true;

            // and notice that curr is pointing to
            //     this node that has the desired data
            
        }
        else
        {
            curr = curr->get_next();
        }
    }

    if (found == true)
    {
        return curr;
    }
    else
    {
        return NULL;
    }
}

/*---
  one of MANY possible insert-in-middle function possibilities!

  signature: insert_after: Node* NodeDataType -> void
  purpose: expects a pointer to a node in a linked list
     and a desired element to add to the list AFTER the
     given node, has the side-effect of adding a new node
     with that desired element AFTER the given node (without
     losing elements after the given node), and returns nothing.
  tests:
    Node *stuff = NULL;
    insert_at_front(stuff, 300);
    insert_at_front(stuff, 400);
    insert_at_front(stuff, 250);
    insert_at_front(stuff, 400);
    insert_at_front(stuff, 25);

    Node *node_before = search_for(stuff, 250);
    insert_after(node_before, 8);
    if I call
    print_list(stuff);
    ....should see the elements 25, 400, 250, 8, 400, 300

    node_before = search_for(stuff, 300);
    insert_after(node_before, 16);
    if I call
    print_list(stuff);
    ....should see the elements 25, 400, 250, 8, 400, 300, 16

---*/

void insert_after(Node *existing_node, NodeDataType new_element)
{
    Node *new_val_node;
    new_val_node = new Node(new_element, existing_node->get_next());

    /* or if you prefer:
    Node *new_val_node = new Node;
    new_val_node->set_data(new_element);
    new_val_node->set_next( existing_node->get_next() );
    */

    existing_node->set_next(new_val_node);
}

/*---
  signature: delete_node: Node*& Node*& -> void
  purpose: expects a pointer to the beginning of a linked
      list and a pointer to a node IN that list to
      be removed, BOTH PASS BY REF, has the side-effects
      of removing that mode from the list
      (changing the starting pointer to point to the 2nd
      element if it was the first node), and freeing that
      removed node's memory, and returns nothing.
---*/

void delete_node(Node*& head, Node*& node_to_remove)
{
    // what if the node to remove is the first node?

    if (head == node_to_remove)
    {
        // change head to point to SECOND mode in list

        head = head->get_next();

        delete node_to_remove;
        node_to_remove = NULL;
    }

    // what if node to remove is NOT the first node?

    else
    {
        Node *before;

        before = head;

        while (before->get_next() != node_to_remove)
        {
            before->set_next( before->get_next() );
        }

        if (node_to_remove->get_next() == NULL)
        {
            before->set_next(NULL);
        }
        else
        {
            before->set_next( node_to_remove->get_next() );
        }

        delete node_to_remove;
        node_to_remove = NULL;
    }
}