/**
 * 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.activemq.apollo.util.list;

/**
 * Provides a base class for you to extend when you want object to maintain a
 * doubly linked list to other objects without using a collection class.
 * 
 * @author chirino
 */
public class LinkedNode<T extends LinkedNode<T>> {

    protected LinkedNodeList<T> list;
    protected T next;
    protected T prev;

    public LinkedNode() {
    }

    @SuppressWarnings("unchecked")
    final private T getThis() {
        return (T) this;
    }

    final  public T getHeadNode() {
        return list.head;
    }

    final public T getTailNode() {
        return list.head.prev;
    }

    final public T getNext() {
        return isTailNode() ? null : next;
    }

    final public T getPrevious() {
        return isHeadNode() ? null : prev;
    }

    final public T getNextCircular() {
        return next;
    }

    final public T getPreviousCircular() {
        return prev;
    }

    final private void assertLinked() {
        assert  list!=null && !list.isEmpty() : "Node is not linked";
    }

    final public boolean isHeadNode() {
        assertLinked();
        return list.head == this;
    }

    final public boolean isTailNode() {
        assertLinked();
        return list.head.prev == this;
    }

    /**
     * @param node
     *            the node to link after this node.
     * @return this
     */
    final public void linkAfter(T node) {
        if (node == this) {
            throw new IllegalArgumentException("You cannot link to yourself");
        }
        if (node.list != null) {
            throw new IllegalArgumentException("You only insert nodes that are not in a list");
        }
        if (list == null) {
            throw new IllegalArgumentException("This node is not yet in a list");
        }

        node.list = list;

        // given we linked this<->next and are inserting node in between
        node.prev = getThis(); // link this<-node
        node.next = next; // link node->next
        next.prev = node; // link node<-next
        next = node; // this->node
        list.size++;
    }

    /**
     * @param rightList
     *            the node to link after this node.
     * @return this
     */
    final public void linkAfter(LinkedNodeList<T> rightList) {

        if (rightList == list) {
            throw new IllegalArgumentException("You cannot link to yourself");
        }
        if (list == null) {
            throw new IllegalArgumentException("This node is not yet in a list");
        }

        T rightHead = rightList.head;
        T rightTail = rightList.head.prev;
        list.reparent(rightList);

        // given we linked this<->next and are inserting list in between
        rightHead.prev = getThis(); // link this<-list
        rightTail.next = next; // link list->next
        next.prev = rightTail; // link list<-next
        next = rightHead; // this->list
    }

    /**
     * @param node
     *            the node to link after this node.
     * @return
     * @return this
     */
    final public void linkBefore(T node) {

        if (node == this) {
            throw new IllegalArgumentException("You cannot link to yourself");
        }
        if (node.list != null) {
            throw new IllegalArgumentException("You only insert nodes that are not in a list");
        }
        if (list == null) {
            throw new IllegalArgumentException("This node is not yet in a list");
        }

        node.list = list;

        // given we linked prev<->this and are inserting node in between
        node.next = getThis(); // node->this
        node.prev = prev; // prev<-node
        prev.next = node; // prev->node
        prev = node; // node<-this

        if (this == list.head) {
            list.head = node;
        }
        list.size++;
    }

    /**
     * @param leftList
     *            the node to link after this node.
     * @return
     * @return this
     */
    final public void linkBefore(LinkedNodeList<T> leftList) {

        if (leftList == list) {
            throw new IllegalArgumentException("You cannot link to yourself");
        }
        if (list == null) {
            throw new IllegalArgumentException("This node is not yet in a list");
        }

        T leftHead = leftList.head;
        T leftTail = leftList.head.prev;
        list.reparent(leftList);

        // given we linked prev<->this and are inserting list in between
        leftTail.next = getThis(); // list->this
        leftHead.prev = prev; // prev<-list
        prev.next = leftHead; // prev->list
        prev = leftTail; // list<-this

        if (isHeadNode()) {
            list.head = leftHead;
        }
    }

    final public void linkToTail(LinkedNodeList<T> target) {
        if (list != null) {
            throw new IllegalArgumentException("This node is already linked to a node");
        }

        if (target.head == null) {
            next = prev = target.head = getThis();
            list = target;
            list.size++;
        } else {
            target.head.prev.linkAfter(getThis());
        }
    }

    final public void linkToHead(LinkedNodeList<T> target) {
        if (list != null) {
            throw new IllegalArgumentException("This node is already linked to a node");
        }

        if (target.head == null) {
            next = prev = target.head = getThis();
            list = target;
            list.size++;
        } else {
            target.head.linkBefore(getThis());
        }
    }

    /**
     * Removes this node out of the linked list it is chained in.
     */
    final public boolean unlink() {

        // If we are allready unlinked...
        if (list == null) {
            return false;
        }

        if (getThis() == prev) {
            // We are the only item in the list
            list.head = null;
        } else {
            // given we linked prev<->this<->next
            next.prev = prev; // prev<-next
            prev.next = next; // prev->next

            if (isHeadNode()) {
                list.head = next;
            }
        }
        list.size--;
        list = null;
        return true;
    }

    /**
     * Splits the list into 2 lists. This node becomes the tail of this list.
     * Then 2nd list is returned.
     * 
     * @return An empty list if this is a tail node.
     */
    final public LinkedNodeList<T> splitAfter() {

        if (isTailNode()) {
            return new LinkedNodeList<T>();
        }

        // Create the new list
        LinkedNodeList<T> newList = new LinkedNodeList<T>();
        newList.head = next;

        // Update the head and tail of the new list so that they point to each
        // other.
        newList.head.prev = list.head.prev; // new list: tail<-head
        newList.head.prev.next = newList.head; // new list: tail->head
        next = list.head; // old list: tail->head
        list.head.prev = getThis(); // old list: tail<-head

        // Update all the nodes in the new list so that they know of their new
        // list owner.
        T n = newList.head;
        newList.size++;
        list.size--;
        do {
            n.list = newList;
            n = n.next;
            newList.size++;
            list.size--;
        } while (n != newList.head);

        return newList;
    }

    /**
     * Splits the list into 2 lists. This node becomes the head of this list.
     * Then 2nd list is returned.
     * 
     * @return An empty list if this is a head node.
     */
    final public LinkedNodeList<T> splitBefore() {

        if (isHeadNode()) {
            return new LinkedNodeList<T>();
        }

        // Create the new list
        LinkedNodeList<T> newList = new LinkedNodeList<T>();
        newList.head = list.head;
        list.head = getThis();

        T newListTail = prev;

        prev = newList.head.prev; // old list: tail<-head
        prev.next = getThis(); // old list: tail->head
        newList.head.prev = newListTail; // new list: tail<-head
        newListTail.next = newList.head; // new list: tail->head

        // Update all the nodes in the new list so that they know of their new
        // list owner.
        T n = newList.head;
        newList.size++;
        list.size--;
        do {
            n.list = newList;
            n = n.next;
            newList.size++;
            list.size--;
        } while (n != newList.head);

        return newList;
    }

    final public boolean isLinked() {
        return list != null;
    }

	final public LinkedNodeList<T> getList() {
		return list;
	}

}
