#ifndef SINGLE_LIST_H
#define SINGLE_LIST_H

/*****************************************
 * UW User ID:  pagunnew
 * Submitted for ECE 250
 * Department of Electrical and Computer Engineering
 * University of Waterloo
 * Calender Term of Submission:  Winter 2011
 *
 * By submitting this file, I affirm that
 * I am the author of all modifications to
 * the provided code.
 *****************************************/

#include "ece250.h"
#include "Single_node.h"
#include "Exception.h"

template <typename Object>
class Single_list {
	private:
		Single_node<Object> *list_head;
		Single_node<Object> *list_tail;
		int node_count;

	public:
		Single_list();
		Single_list( const Single_list & );
		~Single_list();

		Single_list &operator = ( const Single_list & );

		// Accessors

		int size() const;
		bool empty() const;

		Object front() const;
		Object back() const;

		Single_node<Object> *head() const;
		Single_node<Object> *tail() const;

		int count( const Object & ) const;

		// Mutators

		void push_front( const Object & );
		void push_back( const Object & );

		Object pop_front();

		int erase( const Object & );

	// Friends

	template <typename T>
	friend std::ostream &operator << ( std::ostream &, const Single_list<T> & );
};

template <typename Object>
Single_list<Object>::Single_list():list_head(0), list_tail(0), node_count(0) {
	// empty constructor
	// Sets all member variables to 0
}

template <typename Object>
Single_list<Object>::Single_list( const Single_list<Object> &list ):list_head(0), list_tail(0), node_count(0) {
    // Copy constructor
	*this = list;
}

template <typename Object>
Single_list<Object>::~Single_list() {
    // Destructor
    // Pop the front until the list is empty
    while ( !empty() )
    {
	 	 pop_front();
    }
}

template <typename Object>
Single_list<Object> &Single_list<Object>::operator = ( const Single_list<Object> &rhs ) {
     // Assignment Operator

	 // Make sure the list isn't linked to itself
	 if ( &rhs == this ) {
	 	 return *this;
	 }

     // Clear the list
	 while ( !empty() ) {
	 	 pop_front();
	 }

     // The list is empty, and is not ready to become rhs
	 // If rhs is empty we are done
	 if ( rhs.empty() ) {
	 	 return *this;
	 }

     // Copy all the nodes over, by pushing the next node to the back of the list.
	 Single_node<Object> *ptr = rhs.head();
	 while(ptr != 0)
     {
        push_back(ptr->retrieve() );
        ptr = ptr->next();
     }

	return *this;
}

template <typename Object>
int Single_list<Object>::size() const {
	// Returns the number of items in the list
	return node_count;
}

template <typename Object>
bool Single_list<Object>::empty() const {
	// Returns true if the list is empty, false otherwise
	return (list_head == 0);
}

template <typename Object>
Object Single_list<Object>::front() const {
    // Returns the object stored in the node pointed to by the head pointer
	// This function throws a underflow if the list is empty
	if (empty())
	{
	    throw underflow();
	}

	return head()->retrieve();
}

template <typename Object>
Object Single_list<Object>::back() const {
	// Returns the object stored in the node pointed to by the tail pointer
	// This function throws a underflow if the list is empty
	if (empty())
	{
	    throw underflow();
	}
	return tail()->retrieve();
}

template <typename Object>
Single_node<Object> *Single_list<Object>::head() const {
	// Returns the head pointer
	return list_head;
}

template <typename Object>
Single_node<Object> *Single_list<Object>::tail() const {
    // Returns the tail pointer
	return list_tail;
}

template <typename Object>
int Single_list<Object>::count( const Object &obj ) const {
	// Returns 1 if the argument is stored by one of the nodes in the linked list
	// and zero otherwise
	for (Single_node<Object> *ptr = head(); ptr!=0; ptr= ptr->next() )
	{
	    if (ptr->retrieve() == obj)
	    {
	        return 1;
	    }
	}
	return 0;
}

template <typename Object>
void Single_list<Object>::push_front( const Object &obj ) {
	// Creates a new Single_node<Object> storing the argument, the next pointer of which is set to
	// the current head pointer. The head pointer is set to this new node. If the list was origionally
	// empty, the tail pointer is set to point to the new node
    list_head = new Single_node<Object>(obj, head());

    // List is empty
    if (size() == 0)
    {
        list_tail = list_head;
    }
    node_count++;
}

template <typename Object>
void Single_list<Object>::push_back( const Object &obj ) {
	// Creates a new Single_node<Object> storing the argument, the next pointer of which is set to
	// the current tail pointer. The tail pointer is set to this new node. If the list was origionally
	// empty, the head pointer is set to point to the new node

    // List is empty
    if (size() == 0)
    {
        list_tail = new Single_node<Object>(obj, 0);
        list_head = list_tail;
    }
    else
    {
        Single_node<Object> *new_node = new Single_node<Object>(obj, 0);
        list_tail->next_node = new_node;
        list_tail = new_node;
    }
    node_count++;
}

template <typename Object>
Object Single_list<Object>::pop_front() {
	// Delete the node at the front of the linked list and, as necessary, update the head and tail pointers.
	// Return the object stored in the node being popped. Throw an underflow exception if the list is empty.

	// Check if linked list is empty
	if  (empty())
	{
	    throw underflow();
	}

	Object temp = front();
	Single_node<Object> *ptr = head();

	// If there is only 1 element left
	if (size() == 1)
	{
	    // List is now empty
        list_head = 0;
        list_tail = 0;
	}
	else
	{
	    list_head = head()->next();
	}
	delete ptr;

    // Update node_count
    node_count = node_count -1;

	return temp;
}

template <typename Object>
int Single_list<Object>::erase( const Object &obj ) {
	// Delete the first node in the linked list which contains the object equal to the argument.
	// Updates the head and tail pointers and the next pointer of any other node within the list.
	// Returns 1 if a node was deleted and 0 otherwise.

	// Check if the head matches the object that needs to be deleted. If so pop the front
	if (front() == obj)
	{
        pop_front();
        return 1;
	}
    else
    {
        Single_node<Object> *ptr = head();
        while(ptr != 0)
        {
            Single_node<Object> *check = ptr->next();
            if(check->retrieve() == obj)
            {
                ptr->next_node = check->next();
                delete check;
                return 1;
            }
            ptr = ptr->next();
        }
    }
	return 0;
}

// You can modify this function however you want:  it will not be tested
template <typename T>
std::ostream &operator << ( std::ostream &out, const Single_list<T> &list ) {
	for ( Single_node<T> *ptr = list.head(); ptr != 0; ptr = ptr->next() ) {
		out << " -> " << ptr->retrieve();
	}

	out << " -> 0";
	return out;
}


// Is an error showing up in ece250.h or elsewhere?
// Did you forget a closing '}' ?

#endif

