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

import com.smaxe.uv.media.core.VideoFrame;
import com.smaxe.uv.media.java.fx.JfxVideoScreen;
import com.smaxe.uv.na.WebcamFactory;
import com.smaxe.uv.na.webcam.IWebcam;

import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;

/**
 * <code>ExJfxNativeAccessWebcam</code> - {@link IWebcam} usage example in JavaFx environment.
 * 
 * @author Andrei Sochirca
 * @see <a href="http://www.smaxe.com/product.jsf?id=juv-webcam-sdk" target="_blank">JUV Webcam SDK</a>
 */
public final class ExJfxNativeAccessWebcam extends Application
{
    /**
     * Entry point.
     * 
     * @param args
     * @throws Exception if an exception occurred
     */
    public static void main(final String[] args) throws Exception
    {
        launch(args);
    }
    
    /**
     * <code>EnumerateWebcamsEventHandler</code> - 'Enumerqtes Webcams' event handler.
     */
    private final class EnumerateWebcamsEventHandler extends Object implements EventHandler<ActionEvent>
    {
        public void handle(final ActionEvent event)
        {
            executor.execute(new Runnable()
            {
                public void run()
                {
                    final List<IWebcam> webcams = WebcamFactory.getWebcams();
                    
                    Platform.runLater(new Runnable()
                    {
                        public void run()
                        {
                            webcamChoiceBox.getItems().setAll(webcams);
                        }
                    });
                }
            });
        }
    };
    
    /**
     * <code>SetWebcamFrameFormatEventHandler</code> - 'Open Webcam' event handler.
     */
    private final class SetWebcamFrameFormatEventHandler extends Object implements EventHandler<ActionEvent>
    {
        // fields
        private final Executor executor;
        private final IWebcam webcam;
        private final IWebcam.FrameFormat format;
        
        /**
         * Constructor.
         * 
         * @param executor
         * @param webcam
         * @param format
         */
        public SetWebcamFrameFormatEventHandler(final Executor executor, final IWebcam webcam, final IWebcam.FrameFormat format)
        {
            this.executor = executor;
            this.webcam = webcam;
            this.format = format;
        }
        
        // EventHandler implementation
        
        public void handle(final ActionEvent event)
        {
            executor.execute(new Runnable()
            {
                public void run()
                {
                    webcam.setFrameFormat(format);
                }
            });
        }
    }
    
    /**
     * <code>OpenWebcamEventHandler</code> - 'Open Webcam' event handler.
     */
    private final class OpenWebcamEventHandler extends Object implements EventHandler<ActionEvent>
    {
        public void handle(final ActionEvent event)
        {
            final IWebcam webcam = webcamChoiceBox.getSelectionModel().getSelectedItem();
            if (webcam == null) return;
            
            final AtomicReference<Stage> stageRef = new AtomicReference<Stage>();
            
            final JfxVideoScreen videoScreen = new JfxVideoScreen();
            
            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)
                            {
                                Platform.runLater(new Runnable()
                                {
                                    public void run()
                                    {
                                        videoScreen.setFrame(frame);
                                        
                                        if (lastFrame.width != frame.width || lastFrame.height != frame.height)
                                        {
                                            final Stage stage = stageRef.get();
                                            
                                            if (stage != null)
                                            {
                                                stage.sizeToScene();
                                            }
                                        }
                                        
                                        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();
                        
                        Platform.runLater(new Runnable()
                        {
                            public void run()
                            {
                                final Stage stage = new Stage();
                                
                                stage.setResizable(false);
                                stage.setScene(new Scene(new BorderPane(videoScreen)));
                                stage.setTitle(webcam.getName());
                                
                                stageRef.set(stage);
                                
                                videoScreen.setOnMouseClicked(new EventHandler<MouseEvent>()
                                {
                                    public void handle(final MouseEvent event)
                                    {
                                        if (event.getClickCount() != 1) return;
                                        if (event.getButton() != MouseButton.SECONDARY) return;
                                        
                                        ContextMenu popup = new ContextMenu();
                                        
                                        MenuItem takeSnapshot = new MenuItem("Take Snapshot");
                                        
                                        takeSnapshot.setOnAction(new EventHandler<ActionEvent>()
                                        {
                                            public void handle(final ActionEvent e)
                                            {
                                                
                                            }
                                        });
                                        
                                        MenuItem mirror = new MenuItem("Mirror");
                                        
                                        mirror.setOnAction(new EventHandler<ActionEvent>()
                                        {
                                            public void handle(final ActionEvent e)
                                            {
                                                videoScreen.mirror(!videoScreen.getMirrorFlag());
                                            }
                                        });
                                        
                                        MenuItem flip = new MenuItem("Flip");
                                        
                                        flip.setOnAction(new EventHandler<ActionEvent>()
                                        {
                                            public void handle(final ActionEvent e)
                                            {
                                                videoScreen.flip(!videoScreen.getFlipFlag());
                                            }
                                        });
                                        
                                        ObservableList<MenuItem> items = popup.getItems();
                                        
                                        items.add(takeSnapshot);
                                        items.add(new SeparatorMenuItem());
                                        items.add(mirror);
                                        items.add(flip);
                                        items.add(new SeparatorMenuItem());
                                        
                                        for (IWebcam.FrameFormat format : supportedFormats)
                                        {
                                            MenuItem mi = new MenuItem("" + format.width + "x" + format.height);
                                            
                                            mi.setOnAction(new SetWebcamFrameFormatEventHandler(executor, webcam, format));
                                            
                                            items.add(mi);
                                        }
                                        
                                        popup.setAutoHide(true);
                                        popup.setHideOnEscape(true);
                                        
                                        popup.show(videoScreen, event.getScreenX(), event.getScreenY());
                                    }
                                });
                                
                                stage.setOnCloseRequest(new EventHandler<WindowEvent>()
                                {
                                    public void handle(final WindowEvent event)
                                    {
                                        webcam.close();
                                    }
                                });
                                
                                stage.show();
                            }
                        });
                    }
                    catch (Exception ex)
                    {
                        ex.printStackTrace();
                    }
                }
            });
        }
    };
    
    // fields
    private ExecutorService executor = null;
    
    // ui
    private ChoiceBox<IWebcam> webcamChoiceBox;
    
    // Application implementation
    
    /**
     * Constructor.
     */
    public ExJfxNativeAccessWebcam()
    {
    }
    
    @Override
    public void init() throws Exception
    {
        super.init();
        
        executor = Executors.newSingleThreadExecutor();
    }
    
    @Override
    public void start(Stage stage) throws Exception
    {
        Scene scene = new Scene(createComponents());
        
        stage.setResizable(false);
        stage.setScene(scene);
        stage.setTitle("Webcam capture options");
        
        new EnumerateWebcamsEventHandler().handle(null);
        
        stage.sizeToScene();
        stage.show();
    }
    
    @Override
    public void stop() throws Exception
    {
        executor.shutdown();
        
        super.stop();
    }
    
    // inner use methods
    /**
     * Creates and places components(class fields) in the container.
     * 
     * @return components container
     */
    private Parent createComponents()
    {
        webcamChoiceBox = new ChoiceBox<IWebcam>();
        
        webcamChoiceBox.setFocusTraversable(false);
        webcamChoiceBox.setPrefWidth(240);
        
        BorderPane layout = new BorderPane();
        
        Button webcamsButton = new Button("Webcams");
        Button openButton = new Button("Open");
        
        webcamsButton.setFocusTraversable(false);
        webcamsButton.setOnAction(new EnumerateWebcamsEventHandler());
        
        openButton.setFocusTraversable(false);
        openButton.setOnAction(new OpenWebcamEventHandler());
        
        BorderPane.setMargin(webcamsButton, new Insets(8, 8, 8, 8));
        BorderPane.setMargin(openButton, new Insets(8, 8, 8, 8));
        
        layout.setLeft(webcamsButton);
        layout.setCenter(webcamChoiceBox);
        layout.setRight(openButton);
        
        return layout;
    }
}