/*
 * File:     NativeMediaPlayerWindowsQT.java
 * Project:  MPI Linguistic Application
 * Date:     03 April 2006
 *
 * Copyright (C) 2001-2006  Max Planck Institute for Psycholinguistics
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package mpi.eudico.client.annotator.player;

import com.jniwrapper.*;

import com.jniwrapper.win32.automation.Automation;
import com.jniwrapper.win32.automation.IDispatch;
import com.jniwrapper.win32.automation.OleContainer;

import com.jniwrapper.win32.automation.types.BStr;
import com.jniwrapper.win32.automation.types.Variant;

import com.jniwrapper.win32.com.CoInit;
import com.jniwrapper.win32.com.ComFunctions;

import com.jniwrapper.win32.com.types.*;

import com.jniwrapper.win32.ole.OleFunctions;

import com.jniwrapper.win32.ole.impl.IOleObjectImpl;

import com.jniwrapper.win32.ole.types.OleVerbs;

import com.jniwrapper.win32.ui.WindowTools;
import com.jniwrapper.win32.ui.Wnd;

import mpi.eudico.client.annotator.ElanLayoutManager;
import mpi.eudico.client.annotator.ElanLocale;

import mpi.eudico.client.annotator.gui.FormattedMessageDlg;

import mpi.eudico.client.mediacontrol.ControllerEvent;
import mpi.eudico.client.mediacontrol.ControllerListener;
import mpi.eudico.client.mediacontrol.ControllerManager;

import mpi.eudico.client.util.TimeFormatter;

import mpi.eudico.server.corpora.clomimpl.abstr.MediaDescriptor;

//import mpi.eudico.client.annotator.nativemedia.qtactivexplugin.*;
//import mpi.eudico.client.annotator.nativemedia.qtactivexplugin.impl.*;
import java.awt.Panel;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.awt.event.MouseAdapter;

import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;


/**
 * The JMF implementation of an elan media player
 */
public class NativeMediaPlayerWindowsQT extends ControllerManager
    implements ElanMediaPlayer, ControllerListener, ActionListener {
    private final static String QTActiveXPlugin_INTERFACE_ID = "{02BF25D5-8C17-4B23-BC80-D3488ABDDC6B}";
    private MediaDescriptor mediaDescriptor;
    private boolean playing;
    private long offset;
    private long duration;
    private float aspectRatio;
    private long millisPerSample;
    private JPopupMenu popup;
    private JMenuItem durationItem;
    protected JMenuItem detachItem;
    private JMenuItem infoItem;
    private ElanLayoutManager layoutManager;
    private boolean detached;
    private final OleContainer oleContainer;
    private Automation automation;
    private VisualComponent visualComponent;
    private long now;
    private float rate;
    private float timeScale;
    private long stopTime;

    /**
     * Create a JMFMediaPlayer for a media URL
     *
     * visual component
     * speelsnelheid controleren, time scale lijkt vreemd
     * na lang stil staan is een nieuwe initialisatie nodig?
     * player rate is niet in te stellen
     * aspect ratio
     * milli seconds per frame
     *
     * @param mediaDescriptor DOCUMENT ME!
     *
     * @throws NoPlayerException DOCUMENT ME!
     */
    public NativeMediaPlayerWindowsQT(MediaDescriptor mediaDescriptor)
        throws NoPlayerException {
        try {
            this.mediaDescriptor = mediaDescriptor;

            if (DefaultLibraryLoader.getInstance().findLibrary("jniwrap") == null) {
                // relative path to the cvs MPI directory which is the user.dir for developers
                DefaultLibraryLoader.getInstance().addPath("lib/java/jniwrapper");
                DefaultLibraryLoader.getInstance().loadLibrary("jniwrap");
            }

            String URLString = mediaDescriptor.mediaURL;
            URLString = "rtsp://nt06.mpi.nl:80/De_Eng.mp4";

            //   	URLString = "rtsp://nt06.mpi.nl:80/SyncTest_hinted.mov";
            //		URLString = "file:///N:/echo2a_6.mp4";
            //		URLString = "file:///N:/SyncTest.mpg";
            System.out.println("url: " + URLString);

            OleFunctions.oleInitialize();
            oleContainer = new OleContainer();
            oleContainer.createObject(CLSID.create(QTActiveXPlugin_INTERFACE_ID));

            //	    	oleContainer.doVerb(OleVerbs.PRIMARY);
            //	   	    oleContainer.doVerb(OleVerbs.SHOW);
            //	    	oleContainer.doVerb(OleVerbs.INPLACEACTIVATE);
            //	    	oleComponent.doVerb(OleVerbs.OPEN);
            //	    	oleContainer.doVerb(OleVerbs.UIACTIVATE);
            //	    	oleComponent.doVerb(OleVerbs.SHOW);	  
            //	    	visualComponent = new VisualComponent();
            //	    	visualComponent.add(oleContainer);
            JFrame sample = new JFrame("Streaming Video");
            sample.getContentPane().add(oleContainer);
            sample.setSize(640, 480);
            sample.setVisible(true);
            oleContainer.doVerb(OleVerbs.SHOW);

            automation = new Automation(oleContainer.getOleObject());

            automation.invoke("SetURL", new Object[] { new BStr(URLString) });

            automation.invoke("SetRectangle",
                new Object[] { new BStr("0, 0, 300, 200") });
            automation.invoke("SetControllerVisible",
                new Object[] { new Int32(0) });
            automation.invoke("SetTime", new Object[] { new Int32(0) });

            //automation.invoke("Rewind");
            //automation.invoke("SetPlayEveryFrame", new Object[] {new Int32(-1)});
            //something is wrong here?
            timeScale = ((float) ((Variant) automation.invoke("GetTimeScale")).getLVal()
                                  .getValue()) / 1000;
            System.out.println("time scale " +
                ((Variant) automation.invoke("GetTimeScale")).getLVal()
                 .getValue());
            System.out.println("time scale " + timeScale);
            System.out.println("movie size " +
                ((Variant) automation.invoke("GetMovieSize")).getLVal()
                 .getValue());

            aspectRatio = 1;
            duration = (long) (((Variant) automation.invoke("GetDuration")).getLVal()
                                .getValue() / timeScale);
            millisPerSample = 40;
            rate = 1.0f;

            start();
            stop();
            automation.invoke("SetTime", new Object[] { new Int32(0) });

            //automation.invoke("Rewind");
            JPopupMenu.setDefaultLightWeightPopupEnabled(false);
            popup = new JPopupMenu();
            infoItem = new JMenuItem(ElanLocale.getString("Player.Info"));
            infoItem.addActionListener(this);
            durationItem = new JMenuItem(ElanLocale.getString("Player.duration") +
                    ":  " + TimeFormatter.toString(getMediaDuration()));
            durationItem.setEnabled(false);
            popup.addSeparator();
            popup.add(infoItem);
            popup.add(durationItem);
        } catch (Exception e) {
            e.printStackTrace();
            throw new NoPlayerException(
                "Problem while creating Quicktime based native player");
        }
    }

    public MediaDescriptor getMediaDescriptor() {
        return mediaDescriptor;
    }

    public void finalize() {
        OleFunctions.oleUninitialize();
    }

    /**
     *
     */
    public String getFrameworkDescription() {
        return "Native Windows Media Framework QT Plugin";
    }

    /**
     * Should be removed from interface?
     */
    public synchronized void controllerUpdate(ControllerEvent event) {
    }

    /**
     *
     */
    public synchronized void playInterval(long startTime, long stopTime) {
        /*
                automation.invoke("SetTime", new Object[] {new Int32(0)});
                System.out.println("mt1 " + getMediaTime());
                automation.invoke("Step", new Object[] {new Int32(1)});
                System.out.println("mt1 " + getMediaTime());
                automation.invoke("SetTime", new Object[] {new Int32(0)});
                System.out.println("mt1 " + getMediaTime());
                automation.invoke("Step", new Object[] {new Int32(10)});
                System.out.println("mt1 " + getMediaTime());
                */
        System.out.println("play interval " + startTime + " - " + stopTime);
        setMediaTime(startTime);
        System.out.println("SetEndTime " +
            (int) ((stopTime * timeScale) + offset));
        setStopTime(stopTime);

        //automation.invoke("SetEndTime", new Object[] {new Int32((int) (stopTime * timeScale + offset))});
        start();
    }

    /**
     *
     */
    public void setStopTime(long stopTime) {
        this.stopTime = stopTime;
        automation.invoke("SetEndTime",
            new Object[] { new Int32((int) ((stopTime * timeScale) + offset)) });
    }

    /**
     * Gets the display Component for this Player.
     *
     * @return DOCUMENT ME!
     */
    public java.awt.Component getVisualComponent() {
        return visualComponent;
    }

    /**
     * Gets the aspectRatio between width and height of the video image
     *
     * @return DOCUMENT ME!
     */
    public float getAspectRatio() {
        return aspectRatio;
    }

    /**
     * Starts the Player as soon as possible. is not synchronized in JMF
     */
    public synchronized void start() {
        // play at start of media if at end of media
        System.out.println("start duration: " + getMediaDuration());
        System.out.println("start current:  " + getMediaTime());

        if ((getMediaDuration() - getMediaTime()) < 40) { // slow startup so maybe > 40
            setMediaTime(0);
        }

        automation.invoke("Play");

        //automation.invoke("SetRate", new Object[] {new DoubleFloat(rate)});
        // make sure all managed controllers are started
        startControllers();

        //        double rate = ((Variant) automation.invoke("GetRate")).getFltVal().getValue();
        while (getRate() == 0) {
            try {
                Thread.sleep(100);

                //	    		rate = ((Variant) automation.invoke("GetRate")).getFltVal().getValue();
                //	    		System.out.println("sleep start, rate: " + getRate());
            } catch (Exception e) {
            }
        }

        playing = true;
        new Thread(new PlayerStateWatcher()).start();
    }

    /**
     * Stop the media player
     */
    public synchronized void stop() {
        automation.invoke("Stop");
        stopControllers();

        //       double rate = ((Variant) automation.invoke("GetRate")).getFltVal().getValue();
        while (getRate() > 0) {
            try {
                Thread.sleep(100);

                //	    		rate = ((Variant) automation.invoke("GetRate")).getFltVal().getValue();
                //	    		System.out.println("sleep stop");
            } catch (Exception e) {
            }
        }

        playing = false;
    }

    /**
     * Tell if this player is playing
     *
     * @return DOCUMENT ME!
     */
    public boolean isPlaying() {
        return playing;
    }

    /**
     * DOCUMENT ME!
     *
     * @return the step size for one frame
     */
    public long getMilliSecondsPerSample() {
        return millisPerSample;
    }

    /**
     * DOCUMENT ME!
     *
     * @param milliSeconds the step size for one frame
     */
    public void setMilliSecondsPerSample(long milliSeconds) {
        millisPerSample = milliSeconds;
    }

    /**
     * Gets the volume as a number between 0 and 1
     * is translated from 0 - 255 from QT
     *
     * @return DOCUMENT ME!
     */
    public float getVolume() {
        float volume = ((Variant) automation.invoke("GetVolume")).getLVal()
                        .getValue();

        return volume / 255;
    }

    /**
     * Sets the volume as a number between 0 and 1
     * is translated to 0 - 255 for QT
     *
     * @param level DOCUMENT ME!
     */
    public void setVolume(float level) {
        automation.invoke("SetVolume",
            new Object[] { new Int32((int) (level * 255)) });
    }

    /**
     * Set the offset to be used in get and set media time for this player
     *
     * @param offset the offset in milli seconds
     */
    public void setOffset(long offset) {
        this.offset = offset;
        mediaDescriptor.timeOrigin = offset;
    }

    /**
     * DOCUMENT ME!
     *
     * @return the offset used by this player
     */
    public long getOffset() {
        return offset;
    }

    /**
     * Sets the Clock's media time in milli seconds. is not synchronized in JMF
     *
     * @param time DOCUMENT ME!
     */
    public synchronized void setMediaTime(long time) {
        if (isPlaying()) {
            stop();
        }

        time = (long) (time * timeScale);

        if (time < 0) {
            time = 0;
        }

        System.out.println("SetTime " + time);
        automation.invoke("SetTime",
            new Object[] { new Int32((int) (time + offset)) });

        setControllersMediaTime(time);
    }

    public void nextFrame() {
        setMediaTime(getMediaTime() + getMilliSecondsPerSample());
    }

    public void previousFrame() {
        setMediaTime(getMediaTime() - getMilliSecondsPerSample());
    }

    /**
     * Gets this Clock's current media time in milli seconds.
     *
     * @return DOCUMENT ME!
     */
    public long getMediaTime() {
        long t = ((Variant) automation.invoke("GetTime")).getLVal().getValue();
        t = (long) (0.5 + (t / timeScale));

        return t - offset;
    }

    /**
     * Gets the current temporal scale factor.
     *
     * @return DOCUMENT ME!
     */
    public float getRate() {
        return (float) ((Variant) automation.invoke("GetRate")).getFltVal()
                        .getValue();
    }

    /**
     * Sets the temporal scale factor.
     *
     * @param rate DOCUMENT ME!
     */
    public synchronized void setRate(float rate) {
        //stop();
        //this.rate = rate;
    }

    /**
     * Get the duaspectRation of the media represented by this object in milli
     * seconds.
     *
     * @return DOCUMENT ME!
     */
    public long getMediaDuration() {
        return duration;
    }

    /**
     * DOCUMENT ME!
     *
     * @param layoutManager DOCUMENT ME!
     */
    public void setLayoutManager(ElanLayoutManager layoutManager) {
        if (this.layoutManager == null) {
            detachItem = new JMenuItem(ElanLocale.getString("Detachable.detach"));
            detachItem.addActionListener(this);
            popup.insert(detachItem, 0);
        }

        this.layoutManager = layoutManager;
    }

    /**
     * DOCUMENT ME!
     */
    public void updateLocale() {
        infoItem.setText(ElanLocale.getString("Player.Info"));
        durationItem.setText(ElanLocale.getString("Player.duration") + ":  " +
            TimeFormatter.toString(getMediaDuration()));

        if (detachItem != null) {
            if (detached) {
                detachItem.setText(ElanLocale.getString("Detachable.attach"));
            } else {
                detachItem.setText(ElanLocale.getString("Detachable.detach"));
            }
        }
    }

    /*
     *
     */
    public void actionPerformed(ActionEvent e) {
        if (e.getSource().equals(detachItem) && (layoutManager != null)) {
            if (detached) {
                layoutManager.attach(getVisualComponent());
                detachItem.setText(ElanLocale.getString("Detachable.detach"));
                detached = false;
            } else {
                layoutManager.detach(getVisualComponent());
                detachItem.setText(ElanLocale.getString("Detachable.attach"));
                detached = true;
            }

            getVisualComponent().addNotify();
        } else if (e.getSource() == infoItem) {
            new FormattedMessageDlg(this);
        }
    }

    public void cleanUpOnClose() {
        // TODO Auto-generated method stub
    }

    private class VisualComponent extends Panel implements ComponentListener,
        HierarchyListener {
        public VisualComponent() {
            addComponentListener(this);
            addHierarchyListener(this);

            //setOpaque(false);
        }

        public void componentResized(ComponentEvent e) {
        }

        public void hierarchyChanged(HierarchyEvent e) {
            System.out.println("hierar");

            if (isDisplayable()) {
                System.out.println("disp");
                oleContainer.doVerb(OleVerbs.SHOW);
            }
        }

        public void addNotify() {
            System.out.println("addNotify");
            super.addNotify();
            oleContainer.doVerb(OleVerbs.SHOW);
        }

        public void removeNotify() {
            System.out.println("removeNotify");
            oleContainer.doVerb(OleVerbs.HIDE);
            super.removeNotify();
        }

        public void componentShown(ComponentEvent e) {
            System.out.println("compShown");
        }

        public void componentHidden(ComponentEvent e) {
            System.out.println("compHidden");
        }

        public void componentMoved(ComponentEvent e) {
        }
    }

    /*
       private class PlayerStateWatcher implements Runnable {
           public void run() {
               double rate = 0;
               do {
                   try {
                       Thread.sleep(100);
                       rate = ((Variant) automation.invoke("GetRate")).getFltVal().getValue();
                   } catch (Exception e) {

                   }
               } while (rate > 0);
               stop();
               automation.invoke("SetEndTime", new Object[] {new Int32((int) (getMediaDuration() * timeScale))});
           }
       }
    */
    private class PlayerStateWatcher implements Runnable {
        public void run() {
            //   		while (playing && getMediaTime() < stopTime) {
            //   			try {
            //  				Thread.sleep(200);
            //  			} catch (Exception e) {
            //  				e.printStackTrace();
            // 			}
            //  		}
            //  		double rate = 0;
            do {
                try {
                    Thread.sleep(100);

                    //		    		rate = ((Variant) automation.invoke("GetRate: " + rate)).getFltVal().getValue();
                } catch (Exception e) {
                }
            } while (getRate() > 0);

            stop();
            setStopTime((long) (getMediaDuration() * timeScale));
        }
    }

    private class MouseHandler extends MouseAdapter {
        /**
         * DOCUMENT ME!
         *
         * @param e DOCUMENT ME!
         */
        public void mouseClicked(java.awt.event.MouseEvent e) {
            if (e.getClickCount() >= 2) {
                if (layoutManager != null) {
                    layoutManager.setFirstPlayer(NativeMediaPlayerWindowsQT.this);
                }

                return;
            }

            JPopupMenu.setDefaultLightWeightPopupEnabled(false);

            if (SwingUtilities.isRightMouseButton(e)) {
                popup.show(getVisualComponent(), e.getPoint().x, e.getPoint().y);
            }
        }
    }
}
