namespace LinkedListLibrary
{
    // class to represent one node in a list
    class ListNode
    {
        private object data; // stores data for this node
        private ListNode next; // stores a reference to the next node

        // constructor to create ListNode that refers to dataValue
        // and is last node in list
        public ListNode(object dataValue)
            : this(dataValue, null)
        {
        } // end default constructor

        // constructor to create ListNode that refers to dataValue
        // and refers to next ListNode in List
        public ListNode(object dataValue, ListNode nextNode)
        {
            data = dataValue;
            next = nextNode;
        } // end constructor
        // property Next
        public ListNode Next
        {
            get
            {
                return next;
            } // end get
            set
            {
                next = value;
            } // end set
        } // end property Next
        // property Data
        public object Data
        {
            get
            {
                return data;
            } // end get
        } // end property Data
    } // end class ListNode
    // class List declaration
    public class List
    {
        private ListNode firstNode;
        private ListNode lastNode;
        private string name; // string like "list" to display
        // construct empty List with specified name
        public List(string listName)
        {
            name = listName;
            firstNode = lastNode = null;
        } // end constructor

        // construct empty List with "list" as its name
        public List()
            : this("list")
        {
        } // end default constructor
        // Insert object at front of List. If List is empty, 
        // firstNode and lastNode will refer to same object.
        // Otherwise, firstNode refers to new node.
        public void InsertAtFront(object insertItem)
        {
            if (IsEmpty())
                firstNode = lastNode = new ListNode(insertItem);
            else
                firstNode = new ListNode(insertItem, firstNode);
        } // end method InsertAtFront
        // Insert object at end of List. If List is empty, 
        // firstNode and lastNode will refer to same object.
        // Otherwise, lastNode's Next property refers to new node.
        public void InsertAtBack(object insertItem)
        {
            if (IsEmpty())
                firstNode = lastNode = new ListNode(insertItem);
            else
                lastNode = lastNode.Next = new ListNode(insertItem);
        } // end method InsertAtBack
        // remove first node from List
        public object RemoveFromFront()
        {
            if (IsEmpty())
                throw new EmptyListException(name);
            object removeItem = firstNode.Data; // retrieve data

            // reset firstNode and lastNode references
            if (firstNode == lastNode)
                firstNode = lastNode = null;
            else
                firstNode = firstNode.Next;
            return removeItem; // return removed data
        } // end method RemoveFromFront
        // remove last node from List
        public object RemoveFromBack()
        {
            if (IsEmpty())
                throw new EmptyListException(name);
            object removeItem = lastNode.Data; // retrieve data
            // reset firstNode and lastNode references
            if (firstNode == lastNode)
                firstNode = lastNode = null;
            else
            {
                ListNode current = firstNode;
                // loop while current node is not lastNode
                while (current.Next != lastNode)
                    current = current.Next; // move to next node
                // current is new lastNode
                lastNode = current;
                current.Next = null;
            } // end else
            return removeItem; // return removed data
        } // end method RemoveFromBack
        // return true if List is empty
        public bool IsEmpty()
        {
            return firstNode == null;
        } // end method IsEmpty
        // output List contents
        public void Print()
        {
            if (IsEmpty())
            {
                Console.WriteLine("Empty " + name);
                return;
            } // end if
            Console.Write("The " + name + " is: ");
            ListNode current = firstNode;
            // output current node data while not at end of list
            while (current != null)
            {
                Console.Write(current.Data + " ");
                current = current.Next;
            } // end while
            Console.WriteLine("\n");
        } // end method Print
    } // end class List
    // class EmptyListException declaration
    public class EmptyListException : ApplicationException
    {
        public EmptyListException(string name)
            : base("The " + name + " is empty")
        {
        } // end constructor
    } // end class EmptyListException
}
