/*
 * @(#)AbstractVideoViewAWT.java  1.4  2008-06-15
 *
 * Copyright (c) 2000-2008 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 ch.randelshofer.gui.ScaleFactorLayout;
import ch.randelshofer.gui.ScaleFactorConstraints;
import org.monte.media.gui.ImagePanel;
import ch.randelshofer.util.ResourceBundleUtil;
import ch.randelshofer.util.PropertyModel;

import java.awt.*;
import java.awt.dnd.*;
import java.awt.event.ActionListener;
import java.awt.event.ItemListener;
import java.awt.event.*;

import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;

import javax.swing.*;
import javax.swing.event.*;

/**
 * This class provides the layout and common menu items for video media content.
 *
 * @author Werner Randelshofer, Hausmatt 10, CH-6405 Immensee, Switzerland
 * @version 1.4 2008-06-15 Improved compatibility with AbstracVideoView. 
 * <br>1.3 2003-04-07 Automatic start of the player when the 'autoPlay'
 * feature is enabled delegated to subclasses.
 * <br>1.1 2002-02-10 Support for scroll bars added. DnD support added.
 * <br>1.0 2000-10-02
 */
public abstract class AbstractVideoViewAWT extends AbstractViewAWT implements ActionListener, ItemListener {

    protected VideoPanel videoPanel;
    private ScrollPane scrollPane; // heavyweight for better performance and compatibility with JMF and QuickTime
    /**
     * Resource bundle for internationalisation.
     */
    private ResourceBundleUtil labels;
    /** Menu items. */
    private JRadioButtonMenuItem ignoreImageAspectItem,  exactImageAspectItem;
    protected JRadioButtonMenuItem backgroundWhiteItem,  backgroundBlackItem,  backgroundCheckerboardItem;
    private JRadioButtonMenuItem ignorePixelAspectItem,  multipliedPixelAspectItem,  exactPixelAspectItem;
    private JCheckBoxMenuItem autoPlayItem;
    private JCheckBoxMenuItem showScrollbarsItem;
    protected JCheckBoxMenuItem playEveryFrameItem;
    protected JCheckBoxMenuItem loopItem;
    protected JCheckBoxMenuItem audioEnabledItem;
    /** Layout constraints for the video component. */
    private ScaleFactorConstraints videoLayoutConstraints;
    public final static int HALF_SIZE = 0;
    public final static int NORMAL_SIZE = 1;
    public final static int DOUBLE_SIZE = 2;
    public final static int SCREEN_SIZE = 3;
    
    protected final static BufferedImage checkerboardTexture;
    static {
        checkerboardTexture = new BufferedImage(16, 16, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = checkerboardTexture.createGraphics();
        g.setColor(Color.white);
        g.fillRect(0,0,16,16);
        g.setColor(new Color(223,223,223));
        g.fillRect(0,0,8,8);
        g.fillRect(8,8,8,8);
        g.dispose();
    }

    /** Initializes the Form */
    public AbstractVideoViewAWT() {
        labels = ResourceBundleUtil.getLAFBundle("ch.randelshofer.view.AbstractVideoViewAWT");
        initComponents();
        initMoreComponents();
    }

    private void initMoreComponents() {
        scrollPane = new ScrollPane(ScrollPane.SCROLLBARS_ALWAYS);

        videoPanel = new VideoPanel();
        // Scale Factor layout informs us everytime it lays out its components.
        videoPanel.setLayout(
                new ScaleFactorLayout() {

                    public void layoutContainer(Container target) {
                        super.layoutContainer(target);
                        updateScaleFactor();
                    }
                });
        videoPanel.setBackground(Color.black);
        add(videoPanel, BorderLayout.CENTER);

        // Constraints are used for image aspect and scale factor properties
        videoLayoutConstraints = new ScaleFactorConstraints(1, 1, true);

        // Readjust controller panel on Macintosh (avoids that size intrude box
        // on Macintosh windows hides the controller partially).
        String lafID = UIManager.getLookAndFeel().getID();
        strutPanel.setVisible(lafID.equals("Platinum") || lafID.startsWith("Mac"));

        // Menu Items for image aspect
        ButtonGroup group = new ButtonGroup();
        group.add(ignoreImageAspectItem = new JRadioButtonMenuItem(labels.getString("ignoreImageAspect")));
        ignoreImageAspectItem.setMnemonic(labels.getMnemonic("ignoreImageAspectMnem"));
        ignoreImageAspectItem.setAccelerator(labels.getKeyStroke("ignoreImageAspectAcc"));
        ignoreImageAspectItem.addItemListener(this);
        group.add(exactImageAspectItem = new JRadioButtonMenuItem(labels.getString("exactImageAspect")));
        exactImageAspectItem.addItemListener(this);
        exactImageAspectItem.setMnemonic(labels.getMnemonic("exactImageAspectMnem"));
        exactImageAspectItem.setAccelerator(labels.getKeyStroke("exactImageAspectAcc"));

        // Menu items for pixel aspect
        group = new ButtonGroup();
        ignorePixelAspectItem = new JRadioButtonMenuItem(labels.getString("ignorePixelAspect"));
        ignorePixelAspectItem.setMnemonic(labels.getMnemonic("ignorePixelAspectMnem"));
        ignorePixelAspectItem.setAccelerator(labels.getKeyStroke("ignorePixelAspectAcc"));
        group.add(ignorePixelAspectItem);
        ignorePixelAspectItem.addItemListener(this);
        group.add(multipliedPixelAspectItem = new JRadioButtonMenuItem(labels.getString("roundedPixelAspect")));
        multipliedPixelAspectItem.setMnemonic(labels.getMnemonic("roundedPixelAspectMnem"));
        multipliedPixelAspectItem.setAccelerator(labels.getKeyStroke("roundedPixelAspectAcc"));
        multipliedPixelAspectItem.addItemListener(this);
        group.add(exactPixelAspectItem = new JRadioButtonMenuItem(labels.getString("exactPixelAspect")));
        exactPixelAspectItem.addItemListener(this);
        exactPixelAspectItem.setMnemonic(labels.getMnemonic("exactPixelAspectMnem"));
        exactPixelAspectItem.setAccelerator(labels.getKeyStroke("exactPixelAspectAcc"));

        // Menu Items for background color
        group = new ButtonGroup();
        group.add(backgroundBlackItem = new JRadioButtonMenuItem(labels.getString("blackBackground")));
        backgroundBlackItem.setMnemonic(labels.getMnemonic("blackBackgroundMnem"));
        backgroundBlackItem.setAccelerator(labels.getKeyStroke("blackBackgroundAcc"));
        group.add(backgroundWhiteItem = new JRadioButtonMenuItem(labels.getString("whiteBackground")));
        backgroundWhiteItem.setMnemonic(labels.getMnemonic("whiteBackgroundMnem"));
        backgroundWhiteItem.setAccelerator(labels.getKeyStroke("whiteBackgroundAcc"));
        group.add(backgroundCheckerboardItem = new JRadioButtonMenuItem(labels.getString("checkerboardBackground")));
        backgroundCheckerboardItem.setMnemonic(labels.getMnem("checkerboardBackground"));
        backgroundCheckerboardItem.setAccelerator(labels.getAcc("checkerboardBackground"));
        backgroundBlackItem.addItemListener(this);
        backgroundWhiteItem.addItemListener(this);
        backgroundCheckerboardItem.addItemListener(this);

        // Menu items for auto-play
        autoPlayItem = new JCheckBoxMenuItem(labels.getString("autoPlay"));
        autoPlayItem.setMnemonic(labels.getMnemonic("autoPlayMnem"));
        autoPlayItem.setAccelerator(labels.getKeyStroke("autoPlayAcc"));
        autoPlayItem.setActionCommand("autoPlay");
        autoPlayItem.addActionListener(this);

        // Menu items for scrollbars
        showScrollbarsItem = new JCheckBoxMenuItem(labels.getString("showScrollbars"));
        showScrollbarsItem.setMnemonic(labels.getMnemonic("showScrollbarsMnem"));
        showScrollbarsItem.setAccelerator(labels.getKeyStroke("showScrollbarsAcc"));
        showScrollbarsItem.setActionCommand("showScrollbars");
        showScrollbarsItem.addActionListener(this);
        
        // Playback items
        playEveryFrameItem = new JCheckBoxMenuItem(labels.getString("playEveryFrame"));
        playEveryFrameItem.setMnemonic(labels.getMnemonic("playEveryFrameMnem"));
        playEveryFrameItem.setAccelerator(labels.getKeyStroke("playEveryFrameAcc"));
        playEveryFrameItem.addItemListener(this);
        loopItem = new JCheckBoxMenuItem(labels.getString("loop"));
        loopItem.setMnemonic(labels.getMnemonic("loopMnem"));
        loopItem.setAccelerator(labels.getKeyStroke("loopAcc"));
        loopItem.addItemListener(this);
        audioEnabledItem = new JCheckBoxMenuItem(labels.getString("audioEnabled"));
        audioEnabledItem.setMnemonic(labels.getMnemonic("audioEnabledMnem"));
        audioEnabledItem.setAccelerator(labels.getKeyStroke("audioEnabledAcc"));
        audioEnabledItem.addItemListener(this);
    }

    protected void setVideoComponent(Component c) {
        videoPanel.removeAll();
        if (c != null) {
            videoPanel.add(c, videoLayoutConstraints);
        }
    //videoPanel.setVisible(c != null);
    //videoPanel.invalidate();
    }

    protected Component getVideoComponent() {
        return (videoPanel.getComponentCount() == 0) ? null : videoPanel.getComponent(0);
    }

    protected void setControllerComponent(Component c) {
        controllerPanel.removeAll();
        if (c != null) {
            controllerPanel.add(c, BorderLayout.CENTER);
        }
        controllerPanel.invalidate();
    }

    protected Component getControllerComponent() {
        return (controllerPanel.getComponentCount() == 0) ? null : controllerPanel.getComponent(0);
    }

    public void mergeViewMenuItems(JMenu m) {
        JMenu m2;
        JMenuItem mi;
        JRadioButtonMenuItem cmi;


        mi = new JMenuItem(labels.getString("startPlayer"));
        mi.setMnemonic(labels.getMnemonic("startPlayerMnem"));
        mi.setAccelerator(labels.getKeyStroke("startPlayerAcc"));
        mi.setActionCommand("start");
        mi.addActionListener(this);
        m.add(mi);
        mi = new JMenuItem(labels.getString("stopPlayer"));
        mi.setMnemonic(labels.getMnemonic("stopPlayerMnem"));
        mi.setAccelerator(labels.getKeyStroke("stopPlayerAcc"));
        mi.setActionCommand("stop");
        mi.addActionListener(this);
        m.add(mi);
        m.add(autoPlayItem);
        m.addSeparator();

        mi = new JMenuItem(labels.getString("halfSize"));
        mi.setMnemonic(labels.getMnemonic("halfSizeMnem"));
        mi.setAccelerator(labels.getKeyStroke("halfSizeAcc"));
        mi.setActionCommand("halfSize");
        mi.addActionListener(this);
        m.add(mi);
        mi = new JMenuItem(labels.getString("normalSize"));
        mi.setMnemonic(labels.getMnemonic("normalSizeMnem"));
        mi.setAccelerator(labels.getKeyStroke("normalSizeAcc"));
        mi.setActionCommand("normalSize");
        mi.addActionListener(this);
        m.add(mi);
        mi = new JMenuItem(labels.getString("doubleSize"));
        mi.setMnemonic(labels.getMnemonic("doubleSizeMnem"));
        mi.setAccelerator(labels.getKeyStroke("doubleSizeAcc"));
        mi.setActionCommand("doubleSize");
        mi.addActionListener(this);
        m.add(mi);
        mi = new JMenuItem(labels.getString("fillScreen"));
        mi.setMnemonic(labels.getMnemonic("fillScreenMnem"));
        mi.setAccelerator(labels.getKeyStroke("fillScreenAcc"));
        mi.setActionCommand("fillScreen");
        mi.addActionListener(this);
        m.add(mi);
        m.addSeparator();

        m2 = new JMenu(labels.getString("imageAspect"));
        m2.setMnemonic(labels.getMnemonic("imageAspectMnem"));
        m2.add(ignoreImageAspectItem);
        m2.add(exactImageAspectItem);
        m.add(m2);

        m2 = new JMenu(labels.getString("pixelAspect"));
        m2.setMnemonic(labels.getMnemonic("pixelAspectMnem"));
        m2.add(ignorePixelAspectItem);
        m2.add(multipliedPixelAspectItem);
        m2.add(exactPixelAspectItem);
        m.add(m2);

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

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

    protected void updateViewProperties() {
        updateImageAspect();
        updatePixelAspect();
        updateBackground();
        updateInitialScaleFactor();
        updateScaleFactor();
    }

    protected void updateApplicationProperties() {
        updateAutoPlay();
        updateShowScrollbars();
    }

    private void updateBackground() {
        String newValue = getViewProperties().getProperty("background", "black");
        if (newValue.equals("white")) {
            videoPanel.setBackground(Color.white);
            videoPanel.setForeground(Color.black);
            videoPanel.setTexture(null);
            backgroundWhiteItem.setSelected(true);
        } else if (newValue.equals("checkerboard")) {
            videoPanel.setBackground(new Color(176, 176, 176));
            videoPanel.setForeground(Color.black);
            videoPanel.setTexture(checkerboardTexture);
            backgroundCheckerboardItem.setSelected(true);
        } else {
            videoPanel.setBackground(Color.black);
            videoPanel.setForeground(Color.white);
            videoPanel.setTexture(null);
            backgroundBlackItem.setSelected(true);
        }
        repaint();
    }

    protected void updateImageAspect() {
        if (hasScrollPane()) {
            videoLayoutConstraints.setScaleToFit(false);
        } else {
            videoLayoutConstraints.setScaleToFit(true);
            String newValue = getViewProperties().getProperty("imageAspect", "exact");
            if (newValue.equals("exact")) {
                videoLayoutConstraints.setMaintainAspectRatio(true);
                exactImageAspectItem.setSelected(true);
            } else {
                videoLayoutConstraints.setMaintainAspectRatio(false);
                ignoreImageAspectItem.setSelected(true);
            }
        }
        videoPanel.invalidate();
        videoPanel.validate();
    }

    protected void updatePixelAspect() {
        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_.setPixelAspectPolicy(policy);
        ignorePixelAspectItem.setSelected(policy == ImagePanel.IGNORE_PIXEL_ASPECT);
        multipliedPixelAspectItem.setSelected(policy == ImagePanel.ROUNDED_PIXEL_ASPECT);
        exactPixelAspectItem.setSelected(policy == ImagePanel.EXACT_PIXEL_ASPECT);
    }

    protected void updateInitialScaleFactor() {
        String newValue = getViewProperties().getProperty("initialScaleFactor", "normal");
        if (newValue.equals("half")) {
            setViewSize(HALF_SIZE);
        } else if (newValue.equals("normal")) {
            setViewSize(NORMAL_SIZE);
        } else if (newValue.equals("double")) {
            setViewSize(DOUBLE_SIZE);
        } else if (newValue.equals("fillScreen")) {
            setViewSize(SCREEN_SIZE);
        }
    }

    protected void updateAutoPlay() {
        autoPlayItem.setSelected(isAutoPlay());
    }

    protected boolean isAutoPlay() {
        return getApplicationProperties().getProperty("autoPlay", "true").equals("true");
    }

    private void updateShowScrollbars() {
        if (hasScrollPane()) {
            remove(videoPanel);
            updateImageAspect();
            add(scrollPane, BorderLayout.CENTER);
            scrollPane.add(videoPanel);
            scrollPane.invalidate();
        } else {
            remove(scrollPane);
            updateImageAspect();
            add(videoPanel, BorderLayout.CENTER);
            videoPanel.invalidate();
        }
        showScrollbarsItem.setSelected(hasScrollPane());
        invalidate();
        validate();
        repaint();
    }

    public boolean hasScrollPane() {
        return getApplicationProperties().getProperty("showScrollbars", "false").equals("true");
    }

    public void setViewSize(int size) {
        PropertyModel p = getViewProperties();
        ScaleFactorConstraints old = videoLayoutConstraints;

        switch (size) {
            case HALF_SIZE:
                videoLayoutConstraints.setConstraints(0.5, 0.5, videoLayoutConstraints.isMaintainAspectRatio());
                p.put("initialScaleFactor", "half");
                break;

            case NORMAL_SIZE:
                videoLayoutConstraints.setConstraints(1.0, 1.0, videoLayoutConstraints.isMaintainAspectRatio());
                p.put("initialScaleFactor", "normal");
                break;

            case DOUBLE_SIZE:
                videoLayoutConstraints.setConstraints(2.0, 2.0, videoLayoutConstraints.isMaintainAspectRatio());
                p.put("initialScaleFactor", "double");
                break;

            case SCREEN_SIZE:
                p.put("initialScaleFactor", "fillScreen");
                if (getVideoComponent() != null && getParent() != null) {
                    Dimension i = getVideoComponent().getPreferredSize();
                    Dimension s = getToolkit().getScreenSize();

                    Container container = getParent();
                    if (container != null) {
                        while (container.getParent() != null) {
                            container = container.getParent();
                        }
                    }

                    Insets insets = container.getInsets();

                    double factor = Math.min(
                            (s.width - insets.left - insets.right) / (double) i.width,
                            (s.height - insets.top - insets.bottom) / (double) i.height);

                    videoLayoutConstraints.setConstraints(factor, factor, videoLayoutConstraints.isMaintainAspectRatio());
                }
        }
        videoPanel.invalidate();
        videoPanel.validate();
    }

    protected void updateScaleFactor() {
        Component c = getVideoComponent();
        if (c != null) {
            Dimension p = c.getPreferredSize();
            Dimension s = c.getSize();
            double sf = Math.min((s.width / (double) p.width), (s.height / (double) p.height));
            getViewProperties().put("title", "@ " + (int) (100 * sf) + "%");
        }
    }

    public abstract void startPlayer();

    public abstract void stopPlayer();

    public void actionPerformed(ActionEvent event) {
        String actionCommand = event.getActionCommand();

        if (actionCommand.equals("start")) {
            startPlayer();
        } else if (actionCommand.equals("stop")) {
            stopPlayer();
        } else if (actionCommand.equals("halfSize")) {
            setViewSize(HALF_SIZE);
            packParent();
        } else if (actionCommand.equals("normalSize")) {
            setViewSize(NORMAL_SIZE);
            packParent();
        } else if (actionCommand.equals("doubleSize")) {
            setViewSize(DOUBLE_SIZE);
            packParent();
        } else if (actionCommand.equals("fillScreen")) {
            setViewSize(SCREEN_SIZE);
            packParent();
        } else if (actionCommand.equals("autoPlay")) {
            getApplicationProperties().put("autoPlay", autoPlayItem.isSelected() ? "true" : "false");
        } else if (actionCommand.equals("showScrollbars")) {
            getApplicationProperties().put("showScrollbars", showScrollbarsItem.isSelected() ? "true" : "false");
        }
    }

    public void itemStateChanged(ItemEvent event) {
        Object source = event.getSource();
        PropertyModel p = getViewProperties();

        if (event.getStateChange() == ItemEvent.SELECTED) {
            if (source == ignorePixelAspectItem) {
                p.put("pixelAspect", "ignore");
            } else if (source == multipliedPixelAspectItem) {
                p.put("pixelAspect", "rounded");
            } else if (source == exactPixelAspectItem) {
                p.put("pixelAspect", "exact");

            } else if (source == ignoreImageAspectItem) {
                p.put("imageAspect", "ignore");
            } else if (source == exactImageAspectItem) {
                p.put("imageAspect", "exact");
            } else if (source == backgroundBlackItem) {
                p.put("background", "black");
            } else if (source == backgroundWhiteItem) {
                p.put("background", "white");
            }
        }
    }

    public void propertyChange(PropertyChangeEvent event) {
        String key = event.getPropertyName();
        Object source = event.getSource();
        if (source == getViewProperties()) {
            Object newValue = event.getNewValue();
            if (key.equals("scaleFactor")) {
                updateScaleFactor();
            } else if (key.equals("imageAspect")) {
                updateImageAspect();
            } else if (key.equals("pixelAspect")) {
                updatePixelAspect();
            } else if (key.equals("background")) {
                updateBackground();
            }
        } else if (source == getApplicationProperties()) {
            if (key.equals("showScrollbars")) {
                updateShowScrollbars();
            }
        }
    }

    protected void packParent() {
        Container container = getParent();
        if (container != null) {
            while (container.getParent() != null) {
                container = container.getParent();
            }
        }
        if (container != null && container instanceof JFrame) {
            ((JFrame) container).pack();
        }
    }

    /**
     * This method adds the given drop target listener to
     * all components of the viewer.
     */
    public void addDropTargetListener(DropTargetListener listener) {
        new DropTarget(this, listener);
        new DropTarget(videoPanel, listener);
        new DropTarget(scrollPane, listener);
        new DropTarget(controllerPanel, listener);
        new DropTarget(strutPanel, listener);
        new DropTarget(southPanel, listener);
        new DropTarget(getVideoComponent(), listener);
        new DropTarget(getControllerComponent(), listener);
    }

    /** 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 FormEditor.
     */
    private void initComponents() {
        setLayout(new java.awt.BorderLayout());

        southPanel = new java.awt.Panel();
        southPanel.setLayout(new java.awt.BorderLayout());

        strutPanel = new java.awt.Panel() {

            public Dimension getPreferredSize() {
                return new Dimension(15, 0);
            }
        };

        southPanel.add(strutPanel, "East");

        controllerPanel = new java.awt.Panel();
        controllerPanel.setLayout(new java.awt.BorderLayout());

        southPanel.add(controllerPanel, "Center");

        add(southPanel, "South");

    }// Variables declaration - do not modify
    private java.awt.Panel southPanel;
    private java.awt.Panel strutPanel;
    private java.awt.Panel controllerPanel;
// End of variables declaration
    protected class VideoPanel extends Panel {
        private BufferedImage texture;

        @Override
        public void paint(Graphics gr) {
            super.paint(gr);
            
            Graphics2D g = (Graphics2D) gr;
            
            // Draw a border around each component on the panel.
            g.setColor(getForeground());
            Component[] components = getComponents();
            for (int i=0; i < components.length; i++) {
                if (components[i].isVisible()) {
                    Rectangle r = components[i].getBounds();
                    g.drawRect(r.x - 1, r.y - 1, r.width + 1, r.height + 1);
                    if (texture != null) {
                        Paint p = new TexturePaint(texture, new Rectangle(r.x, r.y, texture.getWidth(), texture.getHeight()));
                        g.setPaint(p);
                        g.fillRect(r.x, r.y, r.width, r.height);
                    }
                }
            }
        }

        public void setTexture(BufferedImage texture) {
            this.texture = texture;
            repaint();
        }
    }
}
