package lcm.lcm;

import java.io.*;
import java.util.*;

/**
 * Accumulates received LCM messages in a queue.
 * <p>
 * {@link LCM} normally delivers messages asynchronously by invoking the 
 * {@link LCMSubscriber#messageReceived messageReceived} 
 * method on a subscriber as soon as a message is received.  This class provides an 
 * alternate way to receive messages by storing them in an internal queue, and then
 * delivering them to synchronously to the user.
 * <p>
 * The aggregator has configurable limits.  If too many messages are aggregated
 * without having been retrieved, then older messages are discarded.
 */
public class MessageAggregator
    implements LCMSubscriber
{
    /**
     * A received message.
     */
    public class Message {
	/**
	 * The raw data bytes of the message body.
	 */
	final public byte[] data;
	/**
	 * Channel on which the message was received.
	 */
	final public String channel;
	public Message(String channel_, byte[] data_)
	{
	    data = data_;
	    channel = channel_;
	}
    }

    ArrayDeque<Message> messages = new ArrayDeque<Message>();
    long queue_data_size = 0;
    long max_queue_data_size = 100 * (1 << 20); // 100 megabytes
    int max_queue_length = Integer.MAX_VALUE;

    /**
     * Internal method, called by LCM when a message is received.
     */
    public synchronized void messageReceived(LCM lcm, String channel, 
					     LCMDataInputStream dins)
    {
	try {
	    byte data[] = new byte[dins.available()];
	    dins.readFully(data);

	    messages.addLast(new Message(channel, data));
	    queue_data_size += data.length;

	    while(queue_data_size > max_queue_data_size ||
		  messages.size() > max_queue_length) {
		Message to_remove = messages.removeFirst();
		queue_data_size -= to_remove.data.length;
	    }

	    notify();
	} catch (IOException xcp) {}
    }

    /**
     * Sets the maximum amount of memory that will be used to store messages.
     *
     * This is an alternative way to limit the messages stored by the
     * aggregator.  Messages are discarded oldest-first to ensure that the
     * total size of unretrieved messages stays under this limit.
     *
     * @param val memory limit, in bytes.
     */
    public synchronized void setMaxBufferSize(long val)
    {
	max_queue_data_size = val;
    }

    /**
     * Retrieves the maximum amount of memory that will be used to store messages.
     */
    public synchronized long getMaxBufferSize()
    {
	return max_queue_data_size;
    }

    /**
     * Sets the maximum number of unretrieved message that will be queued up by
     * the aggregator.
     *
     * Messages are discarded oldest-first to ensure that the number of
     * unretrieved messages stays under this limit.
     */
    public synchronized void setMaxMessages(int val) { max_queue_length = val; }
    /**
     * Retrieves the maximum number of unretrieved message that will be queued
     * up by the aggregator.
     */
    public synchronized int getMaxMessages() { return max_queue_length; }

    /**
     * Attempt to retrieve the next received LCM message.
     * @param timeout_ms Max # of milliseconds to wait for a message.  If 0,
     * then don't wait.  If less than 0, then wait indefinitely.
     * @return a Message, or null if no message was received.  
     */
    public synchronized Message getNextMessage(long timeout_ms)
    {
	if(!messages.isEmpty()) {
	    Message m = messages.removeFirst();
	    queue_data_size -= m.data.length;
	    return m;
	}

	if(timeout_ms == 0)
	    return null;

	try {
	    if(timeout_ms > 0)
		wait(timeout_ms);
	    else
		wait();

	    if(!messages.isEmpty()) {
		Message m = messages.removeFirst();
		queue_data_size -= m.data.length;
		return m;
	    }
	} catch (InterruptedException xcp) { }

	return null;
    }

    /**
     * Retrieves the next message, waiting if necessary.
     */
    public synchronized Message getNextMessage()
    {
	return getNextMessage(-1);
    }

    /**
     * Returns the number of received messages waiting to be retrieved.
     */
    public synchronized int numMessagesAvailable()
    {
	return messages.size();
    }
}
