/**
 *  Implementation of a Double Linked List;  forward and backward links point to adjacent Nodes.
 *
 */

 public class LinkedList<T>
 {
     private T data;
     private LinkedList<T> prevNode, nextNode;
 
     /**
      *  Constructs a new element
      *
      * @param  data, data of object
      * @param  node, previous node
      */
     public LinkedList(T data, LinkedList<T> node)
     {
         this.setData(data);
         this.setPrevNode(node);
         this.setNextNode(null);
     }
 
     /**
      *  Clone an object,
      *
      * @param  node  object to clone
      */
     public LinkedList(LinkedList<T> node)
     {
         this.setData(node.data);
         this.setPrevNode(node.prevNode);
         this.setNextNode(node.nextNode);
     }
 
     /**
      *  Setter for T data in DoubleLinkedNode object
      *
      * @param  data, update data of object
      */
     public void setData(T data)
     {
         this.data = data;
     }
 
     /**
      *  Returns T data for this element
      *
      * @return  data associated with object
      */
     public T getData()
     {
         return this.data;
     }
 
     /**
      *  Setter for prevNode in DoubleLinkedNode object
      *
      * @param node, prevNode to current Object
      */
     public void setPrevNode(LinkedList<T> node)
     {
         this.prevNode = node;
     }
 
     /**
      *  Setter for nextNode in DoubleLinkedNode object
      *
      * @param node, nextNode to current Object
      */
     public void setNextNode(LinkedList<T> node)
     {
         this.nextNode = node;
     }
 
 
     /**
      *  Returns reference to previous object in list
      *
      * @return  the previous object in the list
      */
     public LinkedList<T> getPrevious()
     {
         return this.prevNode;
     }
 
     /**
      *  Returns reference to next object in list
      *
      * @return  the next object in the list
      */
     public LinkedList<T> getNext()
     {
         return this.nextNode;
     }
 
 }
/**
 * Queue Iterator
 *
 * 1. "has a" current reference in Queue
 * 2. supports iterable required methods for next that returns a generic T Object
 */
class QueueIterator<T> implements Iterator<T> {
    LinkedList<T> current;  // current element in iteration

    // QueueIterator is pointed to the head of the list for iteration
    public QueueIterator(LinkedList<T> head) {
        current = head;
    }

    // hasNext informs if next element exists
    public boolean hasNext() {
        return current != null;
    }

    // next returns data object and advances to next position in queue
    public T next() {
        T data = current.getData();
        current = current.getNext();
        return data;
    }
}

/**
 * Queue: custom implementation
 * @author     John Mortensen
 *
 * 1. Uses custom LinkedList of Generic type T
 * 2. Implements Iterable
 * 3. "has a" LinkedList for head and tail
 */
public class Queue<T> implements Iterable<T> {
    LinkedList<T> head = null, tail = null;

    /**
     *  Add a new object at the end of the Queue,
     *
     * @param  data,  is the data to be inserted in the Queue.
     */
    public void add(T data) {
        // add new object to end of Queue
        LinkedList<T> tail = new LinkedList<>(data, null);

        if (this.head == null)  // initial condition
            this.head = this.tail = tail;
        else {  // nodes in queue
            this.tail.setNextNode(tail); // current tail points to new tail
            this.tail = tail;  // update tail
        }
    }

    /**
     *  Returns the data of head.
     *
     * @return  data, the dequeued data
     */
    public T delete() {
        T data = this.peek();
        if (this.tail != null) { // initial condition
            this.head = this.head.getNext(); // current tail points to new tail
            if (this.head != null) {
                this.head.setPrevNode(tail);
            }
        }
        return data;
    }

    /**
     * Get the number of elements in the Queue.
     */
    public int size() {
        int count = 0;
        for (T data : this) {
            count++;
        }
        return count;
    }

    /* 
     * Returns true if Queue is empty.
     */
    public boolean isEmpty() {
        return this.head == null;
    }

    /**
     *  Return data in Queue.
     */
    public String toString() {
        String str = "";
        for (T data : this) {
            str += data + " ";
        }
        return str;
    }

    /**
     * Returns data as List.
     */
    public List<T> asList() {
        List<T> list = new ArrayList<>();
        for (T data : this) {
            list.add(data);
        }
        return list;
    }

    /**
     *  Returns the data of head.
     *
     * @return  this.head.getData(), the head data in Queue.
     */
    public T peek() {
        return this.head.getData();
    }

    /**
     *  Returns the head object.
     *
     * @return  this.head, the head object in Queue.
     */
    public LinkedList<T> getHead() {
        return this.head;
    }

    /**
     *  Returns the tail object.
     *
     * @return  this.tail, the last object in Queue
     */
    public LinkedList<T> getTail() {
        return this.tail;
    }

    /**
     *  Returns the iterator object.
     *
     * @return  this, instance of object
     */
    public Iterator<T> iterator() {
        return new QueueIterator<>(this.head);
    }
}
/**
 * Queue Manager
 * 1. "has a" Queue
 * 2. support management of Queue tasks (aka: titling, adding a list, printing)
 */
class QueueManager<T> {
    // queue data
    private final String name; // name of queue
    private int count = 0; // number of objects in queue
    public final Queue<T> queue = new Queue<>(); // queue object

    /**
     *  Queue constructor
     *  Title with empty queue
     */
    public QueueManager(String name) {
        this.name = name;
    }

    /**
     *  Queue constructor
     *  Title with series of Arrays of Objects
     */
    public QueueManager(String name, T[]... seriesOfObjects) {
        this.name = name;
        this.addList(seriesOfObjects);
    }

    /**
     * Add an element to queue
     */
    public void add(T data) {
        System.out.println("Enqueued data: " + data);
        this.queue.add(data);
        this.count++;
    }

    /**
     * Add a list of objects to queue
     */
    public void addList(T[]... seriesOfObjects) {  //accepts multiple generic T lists
        for (T[] objects: seriesOfObjects)
            for (T data : objects) {
                this.queue.add(data);
                this.count++;
            }
    }

    /**
     * Delete an element from queue
     */
    public void delete() {
        // print data else print null
        System.out.println("Dequeued data: " + this.queue.delete());
        this.count--;
    }

    /**
     * Print any array objects from queue
     */
    public void printQueue() {
        System.out.print(this.name + " count: " + count + "\n" + "Data: ");
        for (T data : queue)
            System.out.print(data + " ");
        System.out.println();
    }
}
public class ShuffleQueue {

  public static QueueManager<Object> shuffle(QueueManager<Object> q) {
    // convert the QueueManager to an ArrayList
    List<Object> list = new ArrayList<Object>(q.queue.asList());
    // Shuffle the ArrayList
    Collections.shuffle(list);
    // creating a new QueueManager with the shuffled elements
    QueueManager<Object> shuffled = new QueueManager<Object>("Shuffled");
    shuffled.addList(list.toArray());
    return shuffled;
  }

  public static void main(String[] args) {
    // array of integers
    Object[] numbers = new Integer[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    // creating a QueueManager with the integers
    QueueManager<Object> q = new QueueManager<Object>("Numbers", numbers);

    q.printQueue();
    QueueManager<Object> shuffled = shuffle(q);
    shuffled.printQueue();
    
    // array of strings
    Object[] words = new String[] { "Ephemeral" };
    // creating a QueueManager with the string array
    QueueManager<Object> qw = new QueueManager<Object>("Words", words);

    qw.add("Serendipity");
    qw.add("Quixotic");
    qw.add("Melancholy");
    qw.add("Perfidious");

    qw.printQueue();
    // shuffling the queue and store the result in a new QueueManager
    QueueManager<Object> shuffledwords = shuffle(qw);
    shuffledwords.printQueue();
  }
}

ShuffleQueue.main(null);
Numbers count: 10
Data: 1 2 3 4 5 6 7 8 9 10 
Shuffled count: 10
Data: 9 6 8 10 2 1 4 3 7 5 
Enqueued data: Serendipity
Enqueued data: Quixotic
Enqueued data: Melancholy
Enqueued data: Perfidious
Words count: 5
Data: Ephemeral Serendipity Quixotic Melancholy Perfidious 
Shuffled count: 5
Data: Serendipity Quixotic Ephemeral Perfidious Melancholy