/*
 * @(#)BasicFilterModule.java	1.63 02/08/21
 *
 * Copyright (c) 1996-2002 Sun Microsystems, Inc.  All rights reserved.
 */

package com.sun.media;

import javax.media.*;
import javax.media.Format;
import javax.media.format.RGBFormat;
import javax.media.control.FrameProcessingControl;
import java.awt.*;

/**
 * BasicFilterModule is a module which is not threaded and have one InputConnector and one OutputConnector.
 * It receives data from its input connector, pass the data to the  level 3 plugIn codec and put the
 * result in the OutputConnector.
 * BasicFilterModule can be either Push or Pull driven.
 * The plugIn codec might be media decoder, media encoder, effect etc.
 *
 **/
public  class BasicFilterModule extends BasicModule  {
    protected Codec codec;
    protected InputConnector ic;
    protected OutputConnector oc;
    protected FrameProcessingControl frameControl = null;
    protected float curFramesBehind = 0f;
    protected float prevFramesBehind = 0f;

    protected java.awt.Frame controlFrame;
    protected final boolean VERBOSE_CONTROL=false;

    public BasicFilterModule(Codec c) {
        ic=new BasicInputConnector();
        registerInputConnector("input",ic);
        oc=new BasicOutputConnector();
        registerOutputConnector("output",oc);
        setCodec(c);
        protocol = Connector.ProtocolPush;
        Object control = c.getControl("javax.media.control.FrameProcessingControl");
        if (control instanceof FrameProcessingControl)
            frameControl = (FrameProcessingControl) control;
    }

    public boolean doRealize() {
       if (codec != null) {
           try {
               codec.open();

               if (VERBOSE_CONTROL) {
                   controlFrame=new java.awt.Frame(codec.getName()+"  Control");
                   controlFrame.setLayout(new com.sun.media.controls.VFlowLayout(1));
                   controlFrame.add(new Label(codec.getName()+"  Control",Label.CENTER) );
                   controlFrame.add(new Label( " "));

                   Control[] c=(Control[]) codec.getControls();
                   for (int i=0;i<c.length;i++) {
                       controlFrame.add(c[i].getControlComponent() );
                   }
                   controlFrame.pack();
                   controlFrame.show();
               }

           } catch (ResourceUnavailableException rue) {
               return false;
           }
       }

       return true;
    }

    public boolean doPrefetch(){
        return super.doPrefetch();
    }

    public void doClose() {
        if (codec != null) {
            codec.close();
        }
        if (controlFrame!=null) {
            controlFrame.dispose();
            controlFrame=null;
        }
        /*
        if (monitorFrame != null)
            monitorFrame.dispose();
        */
    }

    public void setFormat(Connector c, Format f) {
        if (c == ic) {
            // Input Connector
            if (codec != null)
                codec.setInputFormat(f);
        } else if (c == oc) {
            if (codec != null)
                codec.setOutputFormat(f);
        }
    }

    /**
     * sets the plugIn codec of this filter.
     * @param codec the plugIn codec (should we specify Codec class or String)
     * @return true if successful
     **/
    public boolean setCodec(String codec){
        return true;
    }

    public boolean setCodec(Codec codec){  // patch until codecmanager exists
        this.codec=codec;
        return true;
    }


    /**
     * @return the plugIn codec of this filter, null if not yet set.
     **/
    public Codec getCodec(){
        return codec;
    }

    public boolean isThreaded(){
        if ( (getProtocol() == Connector.ProtocolSafe) )
            return true;
        return false;

    }

    public Object[] getControls() {
        return codec.getControls();
    }

    public Object getControl(String s) {
        return codec.getControl(s);
    }

    protected void setFramesBehind(float framesBehind) {
        curFramesBehind = framesBehind;
    }
    
    /**
     * A new input format has been detected, we'll check if the existing
     * codec can handle it.  Otherwise, we'll try to re-create a new codec
     * to handle it.
     */
    protected boolean reinitCodec(Format input) {
        // Query the existing plugin to see if it supports the new input.
        if (codec != null) {
            if (codec.setInputFormat(input) != null) {
                // Fine, the existing codec still works.
                return true;
            }
            // close the previous codec.
            codec.close();
            codec = null;
        }

        // Find a new codec that supports the input
        Codec c;
        if ((c = SimpleGraphBuilder.findCodec(input, null, null, null)) == null)
            return false;

        setCodec(c);
        return true;
    }

    protected Buffer    storedInputBuffer,
                      storedOutputBuffer;
    protected boolean readPendingFlag=false,
                      writePendingFlag=false;
    private boolean failed = false;
    private boolean markerSet = false;

    private Object lastHdr = null;


    public void process() {

        // System.out.println("Filter.process: " + codec);

        Buffer inputBuffer, outputBuffer;

        do {
            if (readPendingFlag)
                inputBuffer  = storedInputBuffer;
            else {
                Format incomingFormat;
                inputBuffer = ic.getValidBuffer();
                incomingFormat = inputBuffer.getFormat();               

		if (incomingFormat == null) {
		    // Something's weird, we'll just assume it's the previous
		    // format.
		    incomingFormat = ic.getFormat();
		    inputBuffer.setFormat(incomingFormat);
		}

                if (incomingFormat != ic.getFormat() &&
                    incomingFormat != null &&
                    !incomingFormat.equals(ic.getFormat()) &&
		    !inputBuffer.isDiscard()) {
                    // The format is changed mid-stream!

                    if (writePendingFlag) {
                        // Discard the pending output buffer.
                        storedOutputBuffer.setDiscard(true);
                        oc.writeReport();
                        writePendingFlag = false;
                    }

                    // Attempt to re-initialize the plugin codec.
                    // Bail out if failed.
                    if (!reinitCodec(inputBuffer.getFormat())) {
                        // Failed.
			inputBuffer.setDiscard(true);
                        ic.readReport();
                        failed = true;
                        // Just signal an internal error for now.
                        if (moduleListener != null)
                            moduleListener.formatChangedFailure(this, 
                                ic.getFormat(), inputBuffer.getFormat());
                        return;
                    }

                    Format oldFormat = ic.getFormat();
                    ic.setFormat(inputBuffer.getFormat());
                    if (moduleListener != null)
                        moduleListener.formatChanged(this, oldFormat, 
                                        inputBuffer.getFormat());
                }

                // The marker flag needs to be handle more delicately.
                // For codec that takes multiple input buffers to generate
                // a single output buffer (e.g. RTP depackizer), if there's
                // one input buffer that has the market flag set, the
                // corresponding output buffer will need to have the
                // marker flag set.
                if ((inputBuffer.getFlags() & Buffer.FLAG_SYSTEM_MARKER) != 0) {
                    markerSet = true;
		}

                if (PlaybackEngine.DEBUG && inputBuffer != null) 
                    jmd.moduleIn(this, 0, inputBuffer, true);
            }

            if (writePendingFlag)
                outputBuffer = storedOutputBuffer;
            else {
                outputBuffer = oc.getEmptyBuffer();
                if (outputBuffer != null) {
                    if (PlaybackEngine.DEBUG) 
                        jmd.moduleOut(this, 0, outputBuffer, true);
                    outputBuffer.setLength(0);
                    outputBuffer.setOffset(0);
		    lastHdr = outputBuffer.getHeader();
                }
            }

	    // Copy from input to output.
            outputBuffer.setTimeStamp(inputBuffer.getTimeStamp());
            outputBuffer.setDuration(inputBuffer.getDuration());
            outputBuffer.setSequenceNumber(inputBuffer.getSequenceNumber());
            outputBuffer.setFlags(inputBuffer.getFlags());
	    outputBuffer.setHeader(inputBuffer.getHeader());

	    // Check if we are in the resetted state.
	    if (resetted) {

		// Check if the input buffer contains the zero-length
		// flush flag.  If so, we are almost done.
		if ((inputBuffer.getFlags() & Buffer.FLAG_FLUSH) != 0) {
		    codec.reset();
		    resetted = false;
		}

		// In the resetted state, we won't process any of the
		// data.  We'll just return the buffers unprocessed.
                readPendingFlag = writePendingFlag = false;
		ic.readReport();
		oc.writeReport();
		return;
	    }

            if (failed || inputBuffer.isDiscard()) {

                // Reset the marker flag.
                if (markerSet) {
                    outputBuffer.setFlags(outputBuffer.getFlags() & 
                                        ~Buffer.FLAG_SYSTEM_MARKER);
                    markerSet = false;
                }

                // If the discard flag from the upstream module
                // is set, it has probably dropped a frame.  The
                // curFramesBehind counter should be resetted.
                // Otherwise, too many frames will be dropped.
                curFramesBehind = 0;

                ic.readReport();

                // Propagate the discard flag downstream.
                // This is needed so the renderer can keep track of
                // a correct frame count even for the discarded frames.
                if (!writePendingFlag)
                    oc.writeReport();

                return;
            }

	    if (frameControl != null && curFramesBehind != prevFramesBehind &&
                (inputBuffer.getFlags() & Buffer.FLAG_NO_DROP) == 0) {
                frameControl.setFramesBehind(curFramesBehind);
                prevFramesBehind = curFramesBehind;
            }

	    int rc = 0;

	    try {

		rc = codec.process(inputBuffer , outputBuffer);

	    } catch (Throwable e) {
		Log.dumpStack(e);
		if (moduleListener != null)
		    moduleListener.internalErrorOccurred(this);
	    }

	    if (PlaybackEngine.TRACE_ON && !verifyBuffer(outputBuffer)) {
		System.err.println("verify buffer failed: " + codec);
		Thread.dumpStack();
		if (moduleListener != null)
		    moduleListener.internalErrorOccurred(this);
	    }

	    if ((rc & PlugIn.PLUGIN_TERMINATED) != 0) {
		failed = true;
		if (moduleListener != null)
		    moduleListener.pluginTerminated(this);
                readPendingFlag = writePendingFlag = false;
		ic.readReport();
		oc.writeReport();
		return;
	    }

            if (curFramesBehind > 0f && outputBuffer.isDiscard()) {
                // One frame has been dropped.  We'll need to update
                // the framesBehind count.
                // If we don't do that, we run into trouble that the
                // renderer sometimes cannot update the frames behind
                // count immediately (thread scheduling may prefer this
                // thread to the rendering thread).  As a result, the 
                // decoder will keep dropping frames without a correct 
                // frame count from the renderer.
                curFramesBehind -= 1.0f;
                if (curFramesBehind < 0)
                    curFramesBehind = 0f;

		// We'll also need to propagate the discard flag
		// downstream just so the the downstream module can
		// update their framesbehind info accordingly.  To
		// do that, we override the plugin's return value to
		// make sure that OUTPUT_BUFFER_NOT_FILLED is not used.
		rc = rc & ~codec.OUTPUT_BUFFER_NOT_FILLED;
            }

            if ( (rc & codec.BUFFER_PROCESSED_FAILED) != 0 ) {
                outputBuffer.setDiscard(true);
                // Reset the marker flag.
                if (markerSet) {
                    outputBuffer.setFlags(outputBuffer.getFlags() & 
                                        ~Buffer.FLAG_SYSTEM_MARKER);
                    markerSet = false;
                }
                if (PlaybackEngine.DEBUG) jmd.moduleIn(this, 0, inputBuffer, false);
                ic.readReport();
                if (PlaybackEngine.DEBUG) jmd.moduleOut(this, 0, outputBuffer, false);
                oc.writeReport();
                readPendingFlag = writePendingFlag = false;
                return;
            }

            // Do not propagate the EOM flag if the input buffer is
            // not fully consumed or the output buffer is not fully filled.
            if ( outputBuffer.isEOM() &&
                 ((rc & codec.INPUT_BUFFER_NOT_CONSUMED) != 0 ||
                  (rc & codec.OUTPUT_BUFFER_NOT_FILLED) != 0) ) {
                // We are not quite done yet, so the output buffer
                // is not set to EOM.
                outputBuffer.setEOM(false);
            }

            if ( (rc & codec.OUTPUT_BUFFER_NOT_FILLED) != 0 ) {
                writePendingFlag=true;// next call to getEmptyBuffer will return the same Buffer
                storedOutputBuffer=outputBuffer;
            } else {
                if (PlaybackEngine.DEBUG) jmd.moduleOut(this, 0, outputBuffer, false);
                if (markerSet) {
                    outputBuffer.setFlags(outputBuffer.getFlags() | 
                                        Buffer.FLAG_SYSTEM_MARKER);
                    markerSet = false;
                }
                /*
                if (outputBuffer.getFormat() instanceof RGBFormat)
                    monitorCheck(outputBuffer);
                */
                oc.writeReport();
                writePendingFlag=false;
            }


            if ( ((rc & codec.INPUT_BUFFER_NOT_CONSUMED) != 0 ||
                  (inputBuffer.isEOM() && !outputBuffer.isEOM())) ) {
                readPendingFlag=true; // next call to getValidBuffer will return the same Buffer
                storedInputBuffer=inputBuffer;
            } else {
                if (PlaybackEngine.DEBUG) jmd.moduleIn(this, 0, inputBuffer, false);
		inputBuffer.setHeader(lastHdr);
                ic.readReport();
                readPendingFlag=false;
            }

        } while (readPendingFlag);
    }


    /*
    private Frame monitorFrame = null;
    private Panel monitorPanel = null;
    private long oldTime = (long) -1E+9;
    private Dimension monitorSize = null;
    private void monitorCheck(Buffer buffer) {
        if (buffer.getFormat() instanceof RGBFormat) {
            RGBFormat rgb = (RGBFormat) buffer.getFormat();
            if (monitorFrame == null) {
                monitorSize = rgb.getSize();
                monitorPanel = new Panel() {
                    public void update(Graphics g) {
                    }

                    public void paint(Graphics g) {
                    }

                    public Dimension getPreferredSize() {
                        return monitorSize;
                    }
                };
                monitorFrame = new Frame("Monitor");
                monitorFrame.setLayout( new BorderLayout() );
                monitorFrame.add("Center", monitorPanel);
                monitorFrame.pack();
                
                monitorFrame.setVisible(true);
            }
            long currentTime = System.currentTimeMillis();
            if (currentTime >= oldTime + 1000) {
                oldTime = currentTime;

                Image image = javax.media.util.ImageConverter.convertToImage(buffer);
                Graphics g = monitorPanel.getGraphics();
                g.drawImage(image, 0, 0, monitorPanel);
            }
        }
    }
    */
}
