/*
 * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package gnu.java.zrtp.jmf.transform;

import java.io.*;
import java.net.*;

import javax.media.protocol.*;

/**
 * TransformInputStream implements PushSourceStream. It is used by RTPManager to
 * receive RTP/RTCP packet datas.
 * 
 * In this implementation, we use UDP sockets to receive RTP/RTCP. We listen on
 * the address / port specified by local session address. When one packet is
 * received, it is first reverse transformed through PacketTransformer defined
 * by user. And then returned as normal RTP/RTCP packets to RTPManager.
 * 
 * @author Bing SU (nova.su@gmail.com)
 */
public class TransformInputStream implements PushSourceStream, Runnable {
    /**
     * UDP socket used to receive data.
     */
    private DatagramSocket socket;

    /**
     * User defined PacketTransformer, which is used to reverse transform
     * packets.
     */
    private PacketTransformer transformer;

    /**
     * SourceTransferHandler object which is used to read packets.
     */
    private SourceTransferHandler transferHandler;

    /**
     * Whether this stream is closed. Used to control the termination of worker
     * thread.
     */
    private boolean closed;

    /**
     * Worker thread we use to call transfer handle to received the data
     */
    private Thread recvThread;

    /**
     * Packet receive buffer
     */
    private byte[] buffer = new byte[65535];

    /**
     * Caught an IO exception during read from socket
     */
    private boolean ioError = false;

    private RawPacket pkt = null;

    /**
     * Construct a TransformInputStream based on the receiving socket and
     * PacketTransformer
     * 
     * @param socket
     *            data receiving socket
     * @param transformer
     *            packet transformer used
     */
    public TransformInputStream(DatagramSocket socket,
            PacketTransformer transformer) {
        this.socket = socket;

        this.transformer = transformer;

        this.closed = false;

        this.recvThread = new Thread(this);
        this.recvThread.start();
    }

    /**
     * Close this stream, stops the worker thread.
     */
    public synchronized void close() {
        this.closed = true;
        this.socket.close();
    }

    /*
     * (non-Javadoc)
     * 
     * @see javax.media.protocol.PushSourceStream#read(byte[], int, int)
     */
    public int read(byte[] inBuffer, int offset, int length) throws IOException {
        if (ioError) {
            return -1;
        }
        if (length < pkt.getLength()) {
            throw new IOException("Input buffer not big enough for "
                    + String.valueOf(pkt.getLength()));
        }

        System.arraycopy(pkt.getBuffer(), pkt.getOffset(), inBuffer, offset,
                pkt.getLength());
        return pkt.getLength();
    }

    /*
     * (non-Javadoc)
     * 
     * @see javax.media.protocol.PushSourceStream#setTransferHandler
     *      (javax.media.protocol.SourceTransferHandler)
     */
    public void setTransferHandler(SourceTransferHandler handler) {
        if (this.closed)
            return;

        this.transferHandler = handler;
    }

    /*
     * (non-Javadoc)
     * 
     * @see javax.media.protocol.PushSourceStream#getMinimumTransferSize()
     */
    public int getMinimumTransferSize() {
        return 2 * 1024; // twice the MTU size, just to be safe.
    }

    // ----- Not applicable methods -----

    /*
     * (non-Javadoc)
     * 
     * @see javax.media.protocol.SourceStream#endOfStream()
     */
    public boolean endOfStream() {
        return false;
    }

    /*
     * (non-Javadoc)
     * 
     * @see javax.media.protocol.SourceStream#getContentDescriptor()
     */
    public ContentDescriptor getContentDescriptor() {
        return null;
    }

    /*
     * (non-Javadoc)
     * 
     * @see javax.media.protocol.SourceStream#getContentLength()
     */
    public long getContentLength() {
        return LENGTH_UNKNOWN;
    }

    /*
     * (non-Javadoc)
     * 
     * @see javax.media.Controls#getControl(java.lang.String)
     */
    public Object getControl(String controlType) {
        return null;
    }

    /*
     * (non-Javadoc)
     * 
     * @see javax.media.Controls#getControls()
     */
    public Object[] getControls() {
        return new Object[0];
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Runnable#run()
     */
    public void run() {
        while (!this.closed) {
            DatagramPacket p = new DatagramPacket(this.buffer, 0, 65535);

            try {
                this.socket.receive(p);
            } catch (IOException e) {
                ioError = true;
                break;
            }
            pkt = new RawPacket(this.buffer, 0, p.getLength());
            pkt = this.transformer.reverseTransform(pkt);

            // If the reverse transformed result is not valid,
            // then we will not deliver this packet.
            if (pkt == null) {
                continue;
            }
            if (this.transferHandler != null && !this.closed) {
                this.transferHandler.transferData(TransformInputStream.this);
            }
        }
    }
}
