/*
 * @(#)ANIMVideoView.java  1.7.1  2010-11-24
 *
 * Copyright (c) 1999-2010 Werner Randelshofer, Immensee, Switzerland.
 * All rights reserved.
 *
 * You may not use, copy or modify this file, except in compliance with the
 * license agreement you entered into with Werner Randelshofer.
 * For details see accompanying license terms.
 */
package ch.randelshofer.view;

import org.monte.media.image.ColorModels;
import org.monte.media.gui.ImagePanel;


import org.monte.media.anim.ANIMPlayer;
import org.monte.media.*;


import org.monte.media.StateTracker;
import ch.randelshofer.util.PropertyModel;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.*;

import java.beans.PropertyChangeEvent;

import java.io.IOException;

import java.text.SimpleDateFormat;

import java.util.*;

import javax.activation.*;

import javax.swing.*;
import org.jhotdraw.util.ResourceBundleUtil;

/**
 * This viewer displays videos of type IFF ANIM, IFF ANIM+SLA and IFF ANIM+ANFI.
 *
 * @author  Werner Randelshofer, Hausmatt 10, CH-6405 Immensee, Switzerland
 * @version 1.7.1 2010-11-24 Don't swap speakers by default.
 * <br>1.7 2009-12-24 Added support for color cycling.
 * Fixed merging of pixel aspect sub-menu.
 * <br>1.6 2005-09-16 "Swap Speakers" option added.
 * <br>1.5.1 2005-06-19 Player sometimes waited forever for target state.
 * <br>1.5 2005-01-22 Checkbox menu item "Play Wrapup Frames" added.
 * <br>1.4 2004-12-30 Report the number of palettes in the movie track.
 * <br>1.3 2003-04-27 Support for IFF ANIM+SLA and IFF ANIM+ANFI added.
 * <br>1.2.2 2003-04-02 ANIMPlayer has moved in package org.monte.media.anim
 * from package org.monte.media.
 * <br>1.2 2003-03-30 Made toString more verbose.
 * <br>1.1.1 2002-02-12 Updated for JDD 1.3.
 * <br>1.1   2000-09-28 New superclass: AbstractVideoView.
 * <br>1.0.1 2000-06-11  Text constants are read from a ResourceBundle.
 * <br>1.0   1999-10-19
 */
public class ANIMVideoView extends AbstractVideoView {

    private ANIMPlayer player;
    /** Menu items. */
    protected JCheckBoxMenuItem pingPongItem;
    protected JCheckBoxMenuItem playWrapupFramesItem;
    protected JCheckBoxMenuItem swapSpeakersItem;
    protected JCheckBoxMenuItem debugItem;
    protected JRadioButtonMenuItem fpsOriginalItem;
    protected JRadioButtonMenuItem[] fpsItems;
    /** Menu items. */
    protected JCheckBoxMenuItem colorCyclingItem;
    /** Menu items. */
    protected JCheckBoxMenuItem blendColorCyclesItem;
    private boolean isInitialized = false;
    /**
     * Resource bundle for internationalisation.
     */
    private ResourceBundleUtil labels;

    /** Initializes the Form */
    public ANIMVideoView() {
        labels = ResourceBundleUtil.getBundle("ch.randelshofer.view.AbstractVideoView");
        initMoreComponents();
        // Toggle Color Cycling with TAB key
        // -------------------------------------
        // Normally, the focus subsystem consumes
        // focus traversal keys, such as Tab and Shift Tab.
        // By disabling the focus traversal keys, we can capture
        // the tab key.
        setFocusTraversalKeysEnabled(false);
        setRequestFocusEnabled(true);
        setFocusable(true);
        scrollPane.setFocusTraversalKeysEnabled(false);
        scrollPane.getViewport().setFocusTraversalKeysEnabled(false);
        videoPanel.setFocusTraversalKeysEnabled(false);
        videoPanel.setRequestFocusEnabled(true);
        videoPanel.setFocusable(true);
        videoPanel.addMouseListener(new MouseAdapter() {

            @Override
            public void mousePressed(MouseEvent e) {
                videoPanel.requestFocus();
            }
        });
        getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), "toggleColorCycle");
        getInputMap(WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), "toggleColorCycle");
        getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), "toggleColorCycle");
        getActionMap().put("toggleColorCycle", new AbstractAction() {

            @Override
            public void actionPerformed(ActionEvent e) {
                colorCyclingItem.setSelected(!colorCyclingItem.isSelected());
                getViewProperties().put(
                        "colorCycling", String.valueOf(colorCyclingItem.isSelected()));
            }
        });
    }

    @Override
    public void init() {
        super.init();
        isInitialized = true;
        if (player != null && player.isCached() && isAutoPlay()) {
            player.start();
        }
    }

    private void initMoreComponents() {
        // Menu items for color cycling
        colorCyclingItem = new JCheckBoxMenuItem(labels.getString("colorCycling"));
        colorCyclingItem.setMnemonic(labels.getMnemonic("colorCyclingMnem"));
        colorCyclingItem.setAccelerator(labels.getKeyStroke("colorCyclingAcc"));
        colorCyclingItem.addItemListener(this);
        blendColorCyclesItem = new JCheckBoxMenuItem(labels.getString("blendColorCycles"));
        blendColorCyclesItem.setMnemonic(labels.getMnemonic("blendColorCyclesMnem"));
        blendColorCyclesItem.setAccelerator(labels.getKeyStroke("blendColorCyclesAcc"));
        blendColorCyclesItem.addItemListener(this);
        // Menu items for ping pong playing
        pingPongItem = new JCheckBoxMenuItem(labels.getString("pingPong"));
        pingPongItem.setMnemonic(labels.getMnemonic("pingPongMnem"));
        pingPongItem.setAccelerator(labels.getKeyStroke("pingPongAcc"));
        pingPongItem.addItemListener(this);

        // Menu items for play wrapup frames
        playWrapupFramesItem = new JCheckBoxMenuItem(labels.getString("playWrapupFrames"));
        playWrapupFramesItem.setMnemonic(labels.getMnemonic("playWrapupFramesMnem"));
        playWrapupFramesItem.setAccelerator(labels.getKeyStroke("playWrapupFramesAcc"));
        playWrapupFramesItem.addItemListener(this);

        // Menu items for swap speakers
        swapSpeakersItem = new JCheckBoxMenuItem(labels.getString("swapSpeakers"));
        swapSpeakersItem.setMnemonic(labels.getMnemonic("swapSpeakersMnem"));
        swapSpeakersItem.setAccelerator(labels.getKeyStroke("swapSpeakersAcc"));
        swapSpeakersItem.addItemListener(this);

        // Menu items for original frame rate
        ButtonGroup group = new ButtonGroup();
        fpsOriginalItem = new JRadioButtonMenuItem(labels.getString("fpsOriginal"));
        fpsOriginalItem.setMnemonic(labels.getMnemonic("fpsOriginalMnem"));
        fpsOriginalItem.setAccelerator(labels.getKeyStroke("fpsOriginalAcc"));
        fpsOriginalItem.addItemListener(this);
        group.add(fpsOriginalItem);

        // Menu items for frame rates
        int[] rates = new int[]{10, 12, 25, 30, 50, 60};
        fpsItems = new JRadioButtonMenuItem[rates.length];
        for (int i = 0; i < rates.length; i++) {
            fpsItems[i] = new JRadioButtonMenuItem(labels.getFormatted("fps", rates[i]));
            group.add(fpsItems[i]);
            fpsItems[i].putClientProperty("fps", rates[i]);
            fpsItems[i].addItemListener(this);
        }

        debugItem = new JCheckBoxMenuItem(labels.getString("debug"));
        debugItem.setMnemonic(labels.getMnemonic("debugMnem"));
        debugItem.setAccelerator(labels.getKeyStroke("debugAcc"));
        debugItem.addItemListener(this);
    }

    @Override
    public void mergeViewMenuItems(JMenu m) {
        super.mergeViewMenuItems(m);

        JMenu m2;

        // Menu Items for frame rate
        m.add(new JSeparator(), m.getMenuComponentCount() - 8);
        m.add(loopItem, m.getMenuComponentCount() - 8);
        m.add(pingPongItem, m.getMenuComponentCount() - 8);
        m.add(playEveryFrameItem, m.getMenuComponentCount() - 8);
        m.add(playWrapupFramesItem, m.getMenuComponentCount() - 8);
        m2 = new JMenu(labels.getString("frameRate"));
        m2.setMnemonic(labels.getMnemonic("frameRateMnem"));
        m2.add(fpsOriginalItem);
        for (JRadioButtonMenuItem rbmi : fpsItems) {
            m2.add(rbmi);
        }
        m.add(m2, m.getMenuComponentCount() - 8);
        m.add(new JSeparator(), m.getMenuComponentCount() - 8);
        m.add(audioEnabledItem, m.getMenuComponentCount() - 8);
        m.add(swapSpeakersItem, m.getMenuComponentCount() - 8);

        // Menu Items for color cycling
        m.add(new JSeparator(), m.getMenuComponentCount() - 8);
        m.add(colorCyclingItem, m.getMenuComponentCount() - 8);
        m.add(blendColorCyclesItem, m.getMenuComponentCount() - 8);

        String pixelAspectText = labels.getString("pixelAspect");
        m2 = null;
        for (Component c : m.getMenuComponents()) {
            if (c instanceof JMenu) {
                if (((JMenu) c).getText().equals(pixelAspectText)) {
                    m2 = (JMenu) c;
                    break;
                }
            }
        }
        if (m2 == null) {
            m2 = new JMenu(labels.getString("pixelAspect"));
            m2.setMnemonic(labels.getMnemonic("pixelAspectMnem"));
            m2.add(ignorePixelAspectItem);
            m2.add(multipliedPixelAspectItem);
            m2.add(exactPixelAspectItem);
            m.add(m2);
        } else {
            m2.add(multipliedPixelAspectItem, 2);
        }
        // Menu Items for interpolation
        m2 = new JMenu(labels.getString("interpolation"));
        m2.setMnemonic(labels.getMnemonic("interpolationMnem"));
        m2.add(nearestNeighborItem);
        m2.add(bilinearItem);
        m2.add(bicubicItem);
        m.add(m2);

        m2 = new JMenu(labels.getString("background"));
        m2.setMnemonic(labels.getMnemonic("backgroundMnem"));
        m2.add(backgroundBlackItem);
        m2.add(backgroundWhiteItem);
        m2.add(backgroundCheckerboardItem);
        m.add(m2);

        m.addSeparator();
        m.add(showScrollbarsItem);

        m.addSeparator();
        m.add(debugItem);
    }

    @Override
    public void itemStateChanged(ItemEvent event) {
        super.itemStateChanged(event);

        Object source = event.getSource();
        PropertyModel p = getViewProperties();

        // Handle selection and deselection for JCheckBoxMenuItem's
        if (source == playWrapupFramesItem) {
            getViewProperties().put(
                    "playWrapupFrames", String.valueOf(playWrapupFramesItem.getState()));
        } else if (source == pingPongItem) {
            getViewProperties().put(
                    "pingPong", String.valueOf(pingPongItem.getState()));
        } else if (source == swapSpeakersItem) {
            getViewProperties().put(
                    "swapSpeakers", String.valueOf(swapSpeakersItem.getState()));
        } else if (source == fpsOriginalItem) {
            if (fpsOriginalItem.isSelected()) {
                getViewProperties().put("fps", "-1");
            }
        } else if (source == debugItem) {
            getViewProperties().put(
                    "debug", String.valueOf(debugItem.getState()));
        } else if (source == colorCyclingItem) {
            getViewProperties().put(
                    "colorCycling", String.valueOf(colorCyclingItem.isSelected()));
        } else if (source == blendColorCyclesItem) {
            getViewProperties().put(
                    "blendColorCycles", String.valueOf(blendColorCyclesItem.isSelected()));
        } else {
            if (source instanceof JRadioButtonMenuItem) {
                JRadioButtonMenuItem rbmi = (JRadioButtonMenuItem) source;
                if (rbmi.getClientProperty("fps") instanceof Integer) {
                    getViewProperties().put(
                            "fps", rbmi.getClientProperty("fps").toString());
                }
            }
        }

    }

    @Override
    public void propertyChange(PropertyChangeEvent event) {
        super.propertyChange(event);

        Object source = event.getSource();
        String key = event.getPropertyName();

        if (source == getViewProperties()) {
            if (key.equals("playWrapupFrames")) {
                updatePlayWrapupFrames();
            } else if (key.equals("pingPong")) {
                updatePlayPingPong();
            } else if (key.equals("colorCycling")) {
                updateColorCycling();
            } else if (key.equals("debug")) {
                updateDebug();
            } else if (key.equals("swapSpeakers")) {
                updateSwapSpeakers();
            } else if (key.equals("fps")) {
                updateFPS();
            }
        } else if (source == player) {
            if (key.equals("cached") && isInitialized) {
                if (isAutoPlay()) {
                    startPlayer();
                }
        firePropertyChange(REQUEST_INFO_UPDATE_PROPERTY, false, true);
                
            } else if (key.equals("colorCyclingEnabled") && isInitialized) {
                getViewProperties().put("colorCycling", event.getNewValue().toString());
            }
        }
    }

    @Override
    protected void updateViewProperties() {
        super.updateViewProperties();
        updatePlayWrapupFrames();
        updatePlayPingPong();
        updateSwapSpeakers();
        updateDebug();
        updateFPS();
        updateColorCycling();
    }

    @Override
    protected void updateInterpolation() {
        String newValue = getViewProperties().getProperty("interpolation", "nearestNeighbor");
        ImagePanel videoPanel = (ImagePanel) player.getVisualComponent();
        if (newValue.equals("nearestNeighbor")) {
            videoPanel.setRenderingHints(RENDER_NEAREST_NEIGHBOR);
            nearestNeighborItem.setSelected(true);
        } else if (newValue.equals("bilinear")) {
            videoPanel.setRenderingHints(RENDER_BILINEAR);
            bilinearItem.setSelected(true);
        } else {
            videoPanel.setRenderingHints(RENDER_BICUBIC);
            bicubicItem.setSelected(true);
        }
        repaint();
    }

    @Override
    protected void updatePlayEveryFrame() {
        boolean b = getViewProperties().getProperty("playEveryFrame", "false").equals("true");
        playEveryFrameItem.setSelected(b);
        if (player != null) {
            player.setPlayEveryFrame(b);
        }
    }

    protected void updatePlayWrapupFrames() {
        boolean b = getViewProperties().getProperty("playWrapupFrames", "false").equals("true");
        playWrapupFramesItem.setSelected(b);
        if (player != null) {
            player.setPlayWrapupFrames(b);
        }
        firePropertyChange(REQUEST_INFO_UPDATE_PROPERTY, false, true);
    }

    protected void updateDebug() {
        boolean b = getViewProperties().getProperty("debug", "false").equals("true");
        debugItem.setSelected(b);
        if (player != null) {
            player.setDebug(b);
        }
    }

    protected void updatePlayPingPong() {
        boolean b = getViewProperties().getProperty("pingPong", "false").equals("true");
        pingPongItem.setSelected(b);
        if (player != null) {
            player.setPingPong(b);
        }
    }

    protected void updateColorCycling() {
        boolean b = getViewProperties().getProperty("colorCycling", "true").equals("true");
        colorCyclingItem.setSelected(b);

        if (player != null) {
            player.setColorCyclingStarted(b);
        }

        b = getViewProperties().getProperty("blendColorCycles", "false").equals("true");
        blendColorCyclesItem.setSelected(b);
        if (player != null) {
            player.setBlendedColorCycling(b);
        }
    }

    @Override
    protected void updateLoop() {
        boolean b = getViewProperties().getProperty("loop", "false").equals("true");
        loopItem.setSelected(b);
        if (player != null) {
            player.setLoop(b);
        }
    }

    @Override
    protected void updateAudioEnabled() {
        boolean b = getViewProperties().getProperty("audioEnabled", "true").equals("true");
        //boolean b = player.isAudioEnabled();
        audioEnabledItem.setSelected(b);
        if (player != null) {
            player.setAudioEnabled(b);
        }
    }

    protected void updateSwapSpeakers() {
        boolean b = getViewProperties().getProperty("swapSpeakers", "false").equals("true");
        //boolean b = player.isAudioEnabled();
        swapSpeakersItem.setSelected(b);
        if (player != null) {
            player.setSwapSpeakers(b);
        }
    }

    protected void updateFPS() {
        String value = getViewProperties().getProperty("fps", "-1");
        // XXX - To Do
        int fps = -1;
        try {
            fps = Integer.valueOf(value);
        } catch (NumberFormatException e) {
            // ignore
        }

        boolean valueFound = false;
        for (JRadioButtonMenuItem rbmi : fpsItems) {
            if ((Integer) rbmi.getClientProperty("fps") == fps) {
                rbmi.setSelected(true);
                player.setFramesPerSecond(fps);
                valueFound = true;
                break;
            }
        }
        if (!valueFound) {
            fpsOriginalItem.setSelected(true);
            player.setFramesPerSecond(0);
        }
    }

    @Override
    public synchronized void setCommandContext(String verb, final DataHandler dh)
            throws IOException {
        dispose();
        if (dh != null) {
            if (player != null) {
                player.removePropertyChangeListener(this);
            }

            player = new ANIMPlayer(dh.getInputStream());

            player.addPropertyChangeListener(this);
            updatePlayerConf();
            StateTracker tracker = new StateTracker(player);
            player.setPreferredColorModel(
                    GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration().getColorModel(Transparency.OPAQUE));

            player.addPropertyChangeListener(this);
            player.realize();
            int[] states = {Player.REALIZED, Player.PREFETCHING, Player.PREFETCHED, Player.STARTED, Player.CLOSED};
            tracker.waitForState(states);
            tracker.setStateModel(null);

            setControllerComponent(player.getControlPanelComponent());
            setVideoComponent(player.getVisualComponent());

            // Make the tab key work
            final Component visualComponent = player.getVisualComponent();
            visualComponent.setFocusTraversalKeysEnabled(false);
            ((JComponent) visualComponent).setRequestFocusEnabled(true);
            visualComponent.setFocusable(true);
            visualComponent.addMouseListener(new MouseAdapter() {

                @Override
                public void mousePressed(MouseEvent e) {
                    visualComponent.requestFocus();
                }
            });

            colorCyclingItem.setEnabled(player.isColorCyclingStarted());
            blendColorCyclesItem.setEnabled(player.isColorCyclingStarted());

            if (player.getState() == Player.CLOSED) {
                throw new IOException("Unable to realize player.");
            }
        }
    }

    @Override
    public void addNotify() {
        super.addNotify();
        updatePlayerConf();


    }

    private void updatePlayerConf() {
        if (player != null) {
            GraphicsConfiguration conf = getGraphicsConfiguration();


            if (conf != null) {
                ColorModel cm = conf.getColorModel(Transparency.OPAQUE);
                player.setPreferredColorModel(cm);


            }
        }
    }

    @Override
    protected void updatePixelAspect() {
        if (player != null) {
            String newValue = getViewProperties().getProperty("pixelAspect", "exact");

            int policy;
            if (newValue.equals("ignore")) {
                policy = ImagePanel.IGNORE_PIXEL_ASPECT;
            } else if (newValue.equals("rounded")) {
                policy = ImagePanel.ROUNDED_PIXEL_ASPECT;
            } else {
                policy = ImagePanel.EXACT_PIXEL_ASPECT;
            }

            ((ImagePanel) player.getVisualComponent()).setPixelAspectPolicy(policy);
        }
        super.updatePixelAspect();
    }

    @Override
    public void startPlayer() {
        if (player != null) {
            player.start();
        }
    }

    @Override
    public void stopPlayer() {
        if (player != null) {
            player.stop();
        }
    }

    @Override
    public synchronized void dispose() {
        if (player != null) {
            player.close();
            player = null;


        }
        setControllerComponent(null);
        setVideoComponent(null);
        super.dispose();
    }

    public String getViewName() {
        return "IFF ANIM View";
    }

    @Override
    public String toString() {
        StringBuilder buf = new StringBuilder();
        buf.append(getViewName());

        if (player != null) {
            Image image = ((ImagePanel) player.getVisualComponent()).getImage();
            SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss.SSS");
            formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
            String duration = formatter.format(new Date(player.getTotalDuration()));

            Object comment = image.getProperty("comment", this);

            buf.append("\n\nVideo:");


            if (player.isCached()) {
                buf.append("\n   Video Screen Mode: ").append(image.getProperty("screenMode", this));
                buf.append("\n   Planes: ").append(image.getProperty("nbPlanes", this));
                buf.append("\n   Rendered As: ").append(ColorModels.toString((ColorModel) image.getProperty("renderMode", this)));
                buf.append("\n   Size: ").append(image.getWidth(this)).append(" x ").append(image.getHeight(this)).append(" pixels");
                //buf.append("\n   Aspect Ratio: "+image.getProperty("aspect",this)+" x:y");
                buf.append("\n   Frames: ").append(player.getTimeModel().getMaximum() + 1);
                buf.append("\n   Palettes: ").append(player.getMovieTrack().getNbPalettes());
                buf.append("\n   Duration: ").append(duration);
                buf.append("\n   Compression: ").append(player.getDeltaOperationDescription());
                buf.append("\n   Jiffies: ").append(player.getMovieTrack().getJiffies());


                if (image.getProperty("colorCycling", this) != Image.UndefinedProperty) {
                    buf.append("\n   Color Cycles: ").append(image.getProperty("colorCycling", this));
                }
            } else {
                buf.append("\n   Loading...");
            }

            if (comment != Image.UndefinedProperty && comment != null) {
                buf.append("\n\nComment:");
                buf.append("\n   ").append(comment);
            }
        }
        return buf.toString();

    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    private void initComponents() {//GEN-BEGIN:initComponents

    }//GEN-END:initComponents
    // Variables declaration - do not modify//GEN-BEGIN:variables
    // End of variables declaration//GEN-END:variables
}
