/**
 * Copyright (c) 2008 - 2015 Smaxe Ltd (www.smaxe.com).
 * All rights reserved.
 */

import com.smaxe.uv.media.core.VideoFrame;
import com.smaxe.uv.media.java.swing.JVideoScreen;
import com.smaxe.uv.media.java.video.VideoFrameFactory;
import com.smaxe.uv.na.WebcamFactory;
import com.smaxe.uv.na.webcam.IWebcam;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;

import javax.swing.AbstractAction;
import javax.swing.AbstractListModel;
import javax.swing.ComboBoxModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;

/**
 * <code>ExNativeAccessWebcam</code> - {@link IWebcam} usage example.
 * 
 * @author Andrei Sochirca
 * @see <a href="http://www.smaxe.com/product.jsf?id=juv-webcam-sdk" target="_blank">JUV Webcam SDK</a>
 */
@SuppressWarnings({"rawtypes", "unchecked"})
public final class ExNativeAccessWebcam extends Object
{
    /**
     * Entry point.
     * 
     * @param args
     * @throws Exception if an exception occurred
     */
    public static void main(final String[] args) throws Exception
    {
        final Executor executor = Executors.newSingleThreadExecutor();
        
        final JFrame frame = new JFrame();
        
        final JComboBox webcamComboBox = new JComboBox();
        
        final AbstractAction webcams = new AbstractAction("Webcams")
        {
            private final static long serialVersionUID = 0L;
            
            public void actionPerformed(final ActionEvent e)
            {
                executor.execute(new Runnable()
                {
                    public void run()
                    {
                        final List<IWebcam> webcams = WebcamFactory.getWebcams();
                        
                        SwingUtilities.invokeLater(new Runnable()
                        {
                            public void run()
                            {
                                webcamComboBox.setModel(new WebcamComboModel(webcams));
                            }
                        });
                    }
                });
            }
        };
        
        final JPanel content = new JPanel(new FlowLayout());
        
        content.setBorder(new EmptyBorder(8, 8, 8, 8));
        content.setPreferredSize(new Dimension(640, 48));
        
        content.add(new JButton(webcams));
        content.add(webcamComboBox);
        content.add(new JButton(new AbstractAction("Open")
        {
            private final static long serialVersionUID = -4792981545160764997L;
            
            public void actionPerformed(final ActionEvent e)
            {
                final IWebcam webcam = (IWebcam) webcamComboBox.getSelectedItem();
                if (webcam == null) return;
                
                final AtomicReference<JFrame> frameRef = new AtomicReference<JFrame>();
                
                final JVideoScreen videoScreen = new JVideoScreen();
                
                videoScreen.setAutoScaleOnResize(false);
                
                executor.execute(new Runnable()
                {
                    public void run()
                    {
                        final AtomicReference<VideoFrame> lastFrameRef = new AtomicReference<VideoFrame>();
                        
                        try
                        {
                            webcam.open(new IWebcam.FrameFormat(320, 240), new IWebcam.IListenerX()
                            {
                                private VideoFrame lastFrame = new VideoFrame(0, 0, null);
                                
                                public void onVideoFrame(final VideoFrame frame)
                                {
                                    SwingUtilities.invokeLater(new Runnable()
                                    {
                                        public void run()
                                        {
                                            videoScreen.setFrame(frame);
                                            
                                            if (lastFrame.width != frame.width || lastFrame.height != frame.height)
                                            {
                                                final JFrame frame = frameRef.get();
                                                
                                                if (frame != null) frame.pack();
                                            }
                                            
                                            lastFrame = frame;
                                            
                                            lastFrameRef.set(lastFrame);
                                        }
                                    });
                                }
                                
                                public void onVideoFrameTimeout(final long timeout)
                                {
                                    System.out.println("onVideoFrameTimeout: " + timeout + "ms");
                                }
                                
                                public void onVideoInfo(final float frameRate, final long frames)
                                {
                                    System.out.println("onVideoInfo: frameRate=" + frameRate + ", frames=" + frames);
                                }
                            });
                            
                            final IWebcam.FrameFormat[] supportedFormats = webcam.getFormats();
                            
                            if (supportedFormats != null)
                            {
                                System.out.println("Webcam: " + webcam.getName());
                                System.out.println("Supported formats: ");
                                
                                for (IWebcam.FrameFormat format : supportedFormats)
                                {
                                    System.out.println("=> " + format);
                                }
                            }
                            
                            webcam.startCapture();
                            
                            SwingUtilities.invokeLater(new Runnable()
                            {
                                public void run()
                                {
                                    final JFrame frame = new JFrame();
                                    
                                    frameRef.set(frame);                                    
                                    
                                    frame.getContentPane().setLayout(new BorderLayout());
                                    frame.getContentPane().add(videoScreen, BorderLayout.CENTER);
                                    
                                    frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
                                    frame.setResizable(false);
                                    frame.setTitle(webcam.getName());
                                    
                                    videoScreen.addMouseListener(new MouseAdapter()
                                    {
                                        @Override
                                        public void mouseClicked(final MouseEvent e)
                                        {
                                            final int clickCount = e.getClickCount();
                                            final Object source = e.getSource();
                                            
                                            switch (clickCount)
                                            {
                                                case 1:
                                                {
                                                    if (SwingUtilities.isRightMouseButton(e))
                                                    {
                                                        JPopupMenu popup = new JPopupMenu();
                                                        
                                                        popup.add(new AbstractAction("Take Snapshot")
                                                        {
                                                            private final static long serialVersionUID = 0L;
                                                            
                                                            public void actionPerformed(ActionEvent e)
                                                            {
                                                                new SnapshotDialog(VideoFrameFactory.clone(lastFrameRef.get())).setVisible(true);
                                                            }
                                                        });
                                                        
                                                        popup.addSeparator();
                                                        
                                                        popup.add(new AbstractAction("Mirror")
                                                        {
                                                            private final static long serialVersionUID = 0L;
                                                            
                                                            public void actionPerformed(ActionEvent e)
                                                            {
                                                                videoScreen.mirror(!videoScreen.getMirrorFlag());
                                                            }
                                                        });
                                                        
                                                        popup.add(new AbstractAction("Flip")
                                                        {
                                                            private final static long serialVersionUID = 0L;
                                                            
                                                            public void actionPerformed(ActionEvent e)
                                                            {
                                                                videoScreen.flip(!videoScreen.getFlipFlag());
                                                            }
                                                        });
                                                        
                                                        popup.addSeparator();
                                                        
                                                        for (IWebcam.FrameFormat format : supportedFormats)
                                                        {
                                                            popup.add(new SetWebcamFrameFormat(executor, webcam, format));
                                                        }
                                                        
                                                        if (supportedFormats.length <= 1)
                                                        {
                                                            popup.add(new SetWebcamFrameFormat(executor, webcam, new IWebcam.FrameFormat(320, 240)));
                                                            popup.add(new SetWebcamFrameFormat(executor, webcam, new IWebcam.FrameFormat(640, 480)));
                                                        }
                                                        
                                                        popup.addSeparator();
                                                        
                                                        popup.add(new SetWebcamFrameFormat(executor, webcam, new IWebcam.FrameFormat(512, 512)));
                                                        
                                                        popup.show((Component) source, e.getX(), e.getY());
                                                    }
                                                } break;
                                            }
                                        }
                                    });
                                    
                                    frame.addWindowListener(new WindowAdapter()
                                    {
                                        @Override
                                        public void windowClosing(WindowEvent e)
                                        {
                                            webcam.close();
                                            
                                            frame.dispose();
                                        }
                                    });
                                    
                                    frame.pack();
                                    frame.setVisible(true);
                                }
                            });
                        }
                        catch (Exception ex)
                        {
                            JOptionPane.showMessageDialog(frame,
                                    ex.getMessage(), ex.getMessage(), JOptionPane.WARNING_MESSAGE);
                        }
                    }
                });
            }
        }));
        
        frame.getContentPane().setLayout(new BorderLayout());
        frame.getContentPane().add(content);
        
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.setTitle("Webcam capture options");
        
        frame.addWindowListener(new WindowAdapter()
        {
            @Override
            public void windowClosing(WindowEvent e)
            {
                System.exit(0);
            }
        });
        
        frame.pack();
        frame.setVisible(true);
        
        webcams.actionPerformed(null);
    }
    
    /**
     * <code>SetWebcamFrameFormat</code>
     */
    private final static class SetWebcamFrameFormat extends AbstractAction
    {
        private final static long serialVersionUID = 9182176560922213390L;
        
        // fields
        private final Executor executor;
        private final IWebcam webcam;
        private final IWebcam.FrameFormat format;
        
        /**
         * Constructor.
         * 
         * @param executor
         * @param webcam
         * @param format
         */
        public SetWebcamFrameFormat(final Executor executor, final IWebcam webcam, final IWebcam.FrameFormat format)
        {
            super(format.width + "x" + format.height);
            
            this.executor = executor;
            this.webcam = webcam;
            this.format = format;
        }
        
        // AbstractAction implementation
        
        public void actionPerformed(final ActionEvent e)
        {
            executor.execute(new Runnable()
            {
                public void run()
                {
                    webcam.setFrameFormat(format);
                }
            });
        }
    }
    
    /**
     * <code>SnapshotDialog</code> - snapshot dialog.
     */
    private final static class SnapshotDialog extends JDialog
    {
        private final static long serialVersionUID = -3136925779409522531L;
        
        /**
         * Constructor.
         * 
         * @param videoFrame
         */
        public SnapshotDialog(final VideoFrame videoFrame)
        {
            this.setLayout(new BorderLayout());
            this.setResizable(false);
            this.setTitle("Web snapshot");
            
            JVideoScreen videoScreen = new JVideoScreen();
            
            videoScreen.setFrame(videoFrame);
            
            videoScreen.addMouseListener(new MouseAdapter()
            {
                @Override
                public void mouseClicked(final MouseEvent e)
                {
                    final int clickCount = e.getClickCount();
                    final Object source = e.getSource();
                    
                    switch (clickCount)
                    {
                        case 1:
                        {
                            if (SwingUtilities.isRightMouseButton(e))
                            {
                                JPopupMenu popup = new JPopupMenu();
                                
                                popup.add(new AbstractAction("Save As..")
                                {
                                    private final static long serialVersionUID = 0L;
                                    
                                    public void actionPerformed(ActionEvent e)
                                    {
                                        JFileChooser fileChooser = new JFileChooser();
                                        
                                        fileChooser.setCurrentDirectory(new File("."));
                                        
                                        final int rv = fileChooser.showSaveDialog(getParent());
                                        
                                        if (rv != JFileChooser.APPROVE_OPTION) return;
                                        
                                        final File file = fileChooser.getSelectedFile();
                                        
                                        new Thread(new Runnable()
                                        {
                                            public void run()
                                            {
                                                try
                                                {
                                                    VideoFrameFactory.saveAsJpg(file, videoFrame);
                                                }
                                                catch (IOException e)
                                                {
                                                    e.printStackTrace();
                                                }
                                            }
                                        }).start();
                                    }
                                });
                                
                                popup.show((Component) source, e.getX(), e.getY());
                            }
                        } break;
                    }
                }
            });
            
            this.add(videoScreen, BorderLayout.CENTER);
            this.pack();
        }
    }
    
    /**
     * <code>WebcamComboModel</code> - webcam combo model.
     */
    private final static class WebcamComboModel extends AbstractListModel implements ComboBoxModel
    {
        private final static long serialVersionUID = -8627944517955777531L;
        
        // fields
        private final List<IWebcam> devices;
        // state
        private Object selected = null;
        
        /**
         * Constructor.
         * 
         * @param devices
         */
        public WebcamComboModel(final List<IWebcam> devices)
        {
            this.devices = devices;
        }
        
        // MutableComboBoxModel implementation
        
        public void setSelectedItem(final Object item)
        {
            this.selected = item;
        }
        
        public Object getSelectedItem()
        {
            return selected;
        }
        
        public Object getElementAt(int index)
        {
            return devices.get(index);
        }
        
        public int getSize()
        {
            return devices.size();
        }
    }
    
    /**
     * Constructor.
     */
    private ExNativeAccessWebcam()
    {
    }
}