/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.apex.malhar.lib.appdata.query;

import com.google.common.base.Preconditions;

/**
 * This is a doubly linked list to be used for queueing queries.
 * @param <T> The type of the data used for queueing
 * @since 3.0.0
 */
public class QueueList<T>
{
  /**
   * The number of nodes in the {@link QueueList}.
   */
  private int size = 0;

  /**
   * The head of the queue.
   */
  private QueueListNode<T> head;
  /**
   * The tail of the queue.
   */
  private QueueListNode<T> tail;

  /**
   * A lock which is used to make queueing and dequeueing from this {@link QueueList} thread safe.
   */
  private final Object lock = new Object();

  /**
   * Create a queue list.
   */
  public QueueList()
  {
    //Do nothing
  }

  /**
   * Enqueues the given node to the list.
   * @param node The node to enqueue in the list.
   */
  public void enqueue(QueueListNode<T> node)
  {
    synchronized (lock) {
      Preconditions.checkNotNull(node);
      size++;

      if (head == null) {
        head = node;
        tail = node;
        node.setNext(null);
        node.setPrev(null);
        return;
      }

      tail.setNext(node);
      node.setPrev(tail);
      node.setNext(null);
      tail = node;
    }
  }

  /**
   * Returns the head of the QueueList.
   * @return The head node of the queue list.
   */
  public QueueListNode<T> getHead()
  {
    synchronized (lock) {
      return head;
    }
  }

  /**
   * Removes the given node from the QueueList.
   * @param node The node to remove from this QueueList.
   */
  public void removeNode(QueueListNode<T> node)
  {
    synchronized (lock) {
      size--;

      //Handle the case when adding to the end of list and
      //removing a node in parallel
      if (head == node) {
        if (tail == node) {
          head = null;
          tail = null;
        } else {
          head = node.getNext();
          head.setPrev(null);
        }
      } else {
        if (tail == node) {
          tail = node.getPrev();
          tail.setNext(null);
        } else {
          node.getPrev().setNext(node.getNext());
          node.getNext().setPrev(node.getPrev());
        }
      }
    }
  }

  /**
   * Returns the size of the {@link QueueList}.
   * @return The size of the {@link QueueList}.
   */
  public int getSize()
  {
    return size;
  }

  /**
   * This class represents a node in the QueueList, and holds a data payload provided by the user of type T.
   * @param <T> The type of the data payload of the node.
   */
  public static class QueueListNode<T>
  {
    private QueueListNode<T> prev;
    private QueueListNode<T> next;
    private T payload;

    /**
     * Creates a new queue list node with node data payload.
     */
    public QueueListNode()
    {
    }

    /**
     * Creates a new queue list node with the given data payload.
     * @param payload The data payload for this node.
     */
    public QueueListNode(T payload)
    {
      this.payload = payload;
    }

    /**
     * Gets the previous node that this node points to.
     * @return The previous node that this node points to.
     */
    public QueueListNode<T> getPrev()
    {
      return prev;
    }

    /**
     * Sets the previous node that this node points to.
     * @param prev The previous node that this node will point to.
     */
    public void setPrev(QueueListNode<T> prev)
    {
      this.prev = prev;
    }

    /**
     * Gets the next element this node points to.
     * @return The next element this node points to.
     */
    public QueueListNode<T> getNext()
    {
      return next;
    }

    /**
     * Sets the next element this node points to.
     * @param next The next element this node points to.
     */
    public void setNext(QueueListNode<T> next)
    {
      this.next = next;
    }

    /**
     * Gets the data payload for the node.
     * @return The data payload for the node.
     */
    public T getPayload()
    {
      return payload;
    }

    /**
     * Sets the data payload for the node.
     * @param payload The data payload for the node.
     */
    public void setPayload(T payload)
    {
      this.payload = payload;
    }

    @Override
    public String toString()
    {
      return "QueueListNode{" + "payload=" + payload + '}';
    }
  }
}

