/**
 * $RCSfile$
 * $Revision$
 * $Date$
 *
 * Copyright (C) 2004 Jive Software. All rights reserved.
 *
 * This software is published under the terms of the GNU Public License (GPL),
 * a copy of which is included in this distribution.
 */

package org.jivesoftware.net.spi;

import org.jivesoftware.net.DataConsumer;
import org.jivesoftware.net.DataProducer;
import org.jivesoftware.net.Monitor;

import java.nio.ByteBuffer;
import java.util.Date;

import org.jivesoftware.net.DataConsumer;
import org.jivesoftware.net.DataProducer;

/**
 * A basic data producer that produces data from it's
 * source and adds samples to the monitor.
 *
 * @author Iain Shigeoka
 */
public class BasicDataProducer implements DataProducer {

    /** The source of the data producer, or null if none */
    protected DataProducer source;
    /** The sink for the data producer data, or null if none */
    protected DataConsumer sink;
    /** The mnitor of the data producer data, or null if none */
    protected Monitor monitor;

    /**
     * <p>Create a data producer with the given source, sink, and monitor.</p>
     *
     * @param source The data source or null for none
     * @param sink The data sink or null for none
     * @param monitor The monitor to use or null for none
     */
    public BasicDataProducer(DataProducer source, DataConsumer sink, Monitor monitor){
        this.source = source;
        this.sink = sink;
        this.monitor = monitor;
    }

    /**
     * <p>Create a data producer with the given monitor.</p>
     *
     * @param monitor The monitor to use or null for none
     */
    public BasicDataProducer(Monitor monitor){
        this(null,null,monitor);
    }

    /**
     * <p>Create a data producer without any source, sink, and monitor.</p>
     */
    public BasicDataProducer(){
        this(null,null,null);
    }

    public void setSource(DataProducer source) {
        this.source = source;
    }

    public void setSink(DataConsumer sink) {
        this.sink = sink;
    }

    public void produceAll() {
        if (sink != null){
            ByteBuffer data = produce(0);
            while(data.hasRemaining()){
                sink.consume(data);
                data = produce(0);
            }
        }
    }

    public ByteBuffer produce(long limit) {
        Date start = new Date();

        ByteBuffer data = null;
        boolean success = preProduction(limit);
        if (success){
            data = doProduction(limit);
        }
        if (success && data != null){
            success = postProduction(data,limit);
        }

        if (monitor != null){
            monitor.addSample(data.remaining(),start,new Date());
        }

        if (data != null && sink != null){
            sink.consume(data);
        }

        return data;
    }

    /**
     * <p>Called before production begins to verify production should be done.</p>
     *
     * <p>Override to check the limit size before allowing actual production.</p>
     *
     * @param limit The limit to check before data production or zero for no limit
     * @return True if the production should continue
     */
    protected boolean preProduction(long limit){
        return true;
    }

    /**
     * <p>Conduct the actual production for at most <code>limit</code> bytes.</p>
     *
     * @param limit The limit in bytes to produce
     * @return The bytes read or null for no bytes
     */
    protected ByteBuffer doProduction(long limit){
        ByteBuffer data = null;
        if (source != null){
            data = source.produce(limit);
        }
        return data;
    }

    /**
     * <p>Called after production if verification of the read bytes should
     * be made before returning.</p>
     *
     * @param data The data that was read or null if no data read
     * @param limit The limit in bytes to produce
     * @return True if the production was successful
     */
    protected boolean postProduction(ByteBuffer data, long limit){
        return true;
    }
}
