/** 
 * 
 * Copyright 2004 Protique Ltd
 * 
 * Licensed 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.codehaus.activemq.message.util;

import org.codehaus.activemq.message.Packet;
import org.codehaus.activemq.service.QueueListEntry;
import org.codehaus.activemq.service.impl.DefaultQueueList;

import javax.jms.JMSException;
import java.util.ArrayList;
import java.util.List;

/**
 * MemoryBoundedQueue is a queue bounded by memory usage for Packets
 *
 * @version $Revision: 1.2 $
 */
public class MemoryBoundedQueue implements BoundedPacketQueue {
    private static final int WAIT_TIMEOUT = 100;

    private MemoryBoundedQueueManager queueManager;
    private String name;
    private boolean stopped = false;
    private boolean closed = false;
    private long memoryUsedByThisQueue;
    private Object outLock = new Object();
    private Object inLock = new Object();
    private DefaultQueueList internalList = new DefaultQueueList();

    /**
     * Constructor
     *
     * @param name
     * @param manager
     */
    MemoryBoundedQueue(String name, MemoryBoundedQueueManager manager) {
        this.name = name;
        this.queueManager = manager;
    }

    /**
     * @return the name of this MemoryBoundedQueue
     */
    public String getName() {
        return name;
    }

    /**
     * @return a pretty print of this queue
     */
    public String toString() {
        return "" + name + " , cardinality = " + size() + " memory usage = " + memoryUsedByThisQueue;
    }

    /**
     * @return the number of items held by this queue
     */
    public int size() {
        return internalList.size();
    }

    /**
     * @return an aproximation the memory used by this queue
     */
    public long getLocalMemoryUsedByThisQueue() {
        return memoryUsedByThisQueue;
    }

    /**
     * close and remove this queue from the MemoryBoundedQueueManager
     */
    public void close() {
        try {
            clear();
            closed = true;
            synchronized (outLock) {
                outLock.notifyAll();
            }
            synchronized (inLock) {
                inLock.notifyAll();
            }
        }
        catch (Throwable e) {
            e.printStackTrace();
        }
        finally {
            queueManager.removeMemoryBoundedQueue(getName());
        }
    }

    /**
     * Enqueue a Packet without checking memory usage limits
     *
     * @param packet
     */
    public void enqueueNoBlock(Packet packet) {
        if (!closed) {
            internalList.add(packet);
            incrementMemoryUsed(packet);
            synchronized (outLock) {
                outLock.notify();
            }
        }
    }

    /**
     * Enqueue a Packet to this queue
     *
     * @param packet
     */
    public void enqueue(Packet packet) {
        if (!queueManager.isFull()) {
            enqueueNoBlock(packet);
        }
        else {
            synchronized (inLock) {
                try {
                    while (queueManager.isFull() && !closed) {
                        inLock.wait(WAIT_TIMEOUT);
                    }
                }
                catch (InterruptedException ie) {
                }
            }
            enqueueNoBlock(packet);
        }
    }

    /**
     * Enqueue a packet to the head of the queue with total disregard for memory constraints
     *
     * @param packet
     */
    public final void enqueueFirstNoBlock(Packet packet) {
        if (!closed) {
            internalList.addFirst(packet);
            incrementMemoryUsed(packet);
            synchronized (outLock) {
                outLock.notify();
            }
        }
    }

    /**
     * Enqueue a Packet to the head of the queue
     *
     * @param packet
     * @throws InterruptedException
     */
    public void enqueueFirst(Packet packet) throws InterruptedException {
        if (!queueManager.isFull()) {
            enqueueFirstNoBlock(packet);
        }
        else {
            synchronized (inLock) {
                while (queueManager.isFull() && !closed) {
                    inLock.wait(WAIT_TIMEOUT);
                }
            }
            enqueueFirstNoBlock(packet);
        }
    }

    /**
     * @return the first dequeued Packet or blocks until one is available
     * @throws InterruptedException
     */
    public Packet dequeue() throws InterruptedException {
        Packet result = null;
        synchronized (outLock) {
            while (internalList.isEmpty() && !closed) {
                outLock.wait(WAIT_TIMEOUT);
            }
            result = dequeueNoWait();
        }
        return result;
    }

    /**
     * Dequeues a Packet from the head of the queue
     *
     * @param timeInMillis time to wait for a Packet to be available
     * @return the first Packet or null if none available within <I>timeInMillis </I>
     * @throws InterruptedException
     */
    public Packet dequeue(long timeInMillis) throws InterruptedException {
        Packet result = null;
        if (timeInMillis == 0) {
            result = dequeue();
        }
        else {
            synchronized (outLock) {
                // if timeInMillis is less than zero assume nowait
                long waitTime = timeInMillis;
                long start = (timeInMillis <= 0) ? 0 : System.currentTimeMillis();
                while (!closed) {
                    result = dequeueNoWait();
                    if (result != null || waitTime <= 0) {
                        break;
                    }
                    else {
                        outLock.wait(waitTime);
                        waitTime = timeInMillis - (System.currentTimeMillis() - start);
                    }
                }
            }
        }
        return result;
    }

    /**
     * dequeues a Packet from the head of the queue
     *
     * @return the Packet at the head of the queue or null, if none is available
     * @throws InterruptedException
     */
    public Packet dequeueNoWait() throws InterruptedException {
        Packet packet = null;
        synchronized (outLock) {
            while (stopped && !closed) {
                outLock.wait(WAIT_TIMEOUT);
            }
        }
        packet = (Packet) internalList.removeFirst();
        decrementMemoryUsed(packet);
        if (packet != null) {
            synchronized (inLock) {
                inLock.notify();
            }
        }
        return packet;
    }

    /**
     * @return true if the queue is enabled for dequeing (default = true)
     */
    public boolean isStarted() {
        synchronized (outLock) {
            return stopped == false;
        }
    }

    /**
     * disable dequeueing
     */
    public void stop() {
        synchronized (outLock) {
            stopped = true;
        }
    }

    /**
     * enable dequeueing
     */
    public void start() {
        synchronized (outLock) {
            stopped = false;
            outLock.notifyAll();
        }
        synchronized (inLock) {
            inLock.notifyAll();
        }
    }

    /**
     * Remove a packet from the queue
     *
     * @param packet
     * @return true if the packet was found
     */
    public boolean remove(Packet packet) {
        boolean result = false;
        if (!internalList.isEmpty()) {
            result = internalList.remove(packet);
        }
        if (result) {
            decrementMemoryUsed(packet);
        }
        synchronized (inLock) {
            inLock.notify();
        }
        return result;
    }

    /**
     * Remove a Packet by it's id
     *
     * @param id
     * @return
     */
    public Packet remove(String id) {
        Packet result = null;
        QueueListEntry entry = internalList.getFirstEntry();
        try {
            while (entry != null) {
                Packet p = (Packet) entry.getElement();
                if (p.getId().equals(id)) {
                    result = p;
                    internalList.remove(entry);
                    break;
                }
                entry = internalList.getNextEntry(entry);
            }
        }
        catch (JMSException jmsEx) {
            jmsEx.printStackTrace();
        }
        synchronized (inLock) {
            inLock.notify();
        }
        return result;
    }

    /**
     * remove any Packets in the queue
     */
    public void clear() {
        while (!internalList.isEmpty()) {
            Packet packet = (Packet) internalList.removeFirst();
            decrementMemoryUsed(packet);
        }
        synchronized (inLock) {
            inLock.notifyAll();
        }
    }

    /**
     * @return true if the queue is empty
     */
    public boolean isEmpty() {
        return internalList.isEmpty();
    }

    /**
     * retrieve a Packet at an indexed position in the queue
     *
     * @param index
     * @return
     */
    public Packet get(int index) {
        return (Packet) internalList.get(index);
    }

    /**
     * Retrieve a shallow copy of the contents as a list
     *
     * @return a list containing the bounded queue contents
     */
    public List getContents() {
        Object[] array = internalList.toArray();
        List list = new ArrayList();
        for (int i = 0; i < array.length; i++) {
            list.add(array[i]);
        }
        return list;
    }

    private synchronized void incrementMemoryUsed(Packet packet) {
        if (packet != null) {
            memoryUsedByThisQueue += queueManager.incrementMemoryUsed(packet);
        }
    }

    private synchronized void decrementMemoryUsed(Packet packet) {
        if (packet != null) {
            memoryUsedByThisQueue -= queueManager.decrementMemoryUsed(packet);
        }
    }
}