package cz.fidentis.analyst.canvas;

import com.jogamp.opengl.GLCapabilities;
import com.jogamp.opengl.GLProfile;
import com.jogamp.opengl.awt.GLCanvas;
import cz.fidentis.analyst.face.HumanFace;
import cz.fidentis.analyst.scene.Camera;
import cz.fidentis.analyst.scene.Scene;
import cz.fidentis.analyst.scene.SceneRenderer;
import java.awt.BorderLayout;

import javax.swing.JLayeredPane;
import javax.swing.JPanel;

/**
 * OpenGL canvas.
 * 
 * @author Natalia Bebjakova 
 * @author Radek Oslejsek
 */
public class Canvas extends JPanel {

    // Scene components:
    private Scene scene;
    private final SceneRenderer sceneRenderer;
    private final Camera camera = new Camera();
    
    // Listeners:
    private final CanvasListener listener;
    private final MouseRotationListener manipulator;
    
    // GUI elements:
    private final JLayeredPane layeredPane;
    private final JPanel canvasPanel;
    private final ControlButtons controlButtonsPanel;
    private GLCanvas glCanvas;
    
    
    /**
     * Constructor.
     */
    public Canvas() {
        sceneRenderer = new SceneRenderer();
        listener = new CanvasListener(this);
        manipulator = new MouseRotationListener(this);
        
        setLayout(new BorderLayout());

        initGLCanvas();
        canvasPanel = getCanvasPanel();
        canvasPanel.add(glCanvas);
        
        controlButtonsPanel = getButtonsPanel(this);
        
        layeredPane = getLayeredPane();
        layeredPane.add(canvasPanel, 1);
        //layeredPane.setLayer(controlButtonsPanel, JLayeredPane.MODAL_LAYER);
        layeredPane.add(controlButtonsPanel, 0);
        add(layeredPane, BorderLayout.CENTER);
        
        setDarkBackground(true);
        
        validate();   
    }
    
    /**
     * Initializes the scene for a single face. Can be called only once.
     * 
     * @param face Human face
     * @throws UnsupportedOperationException the the scene is already initiated
     * @throws IllegalArgumentException if the face is missing
     */
    public void initScene(HumanFace face) {
        if (scene != null) {
            throw new UnsupportedOperationException("Scene already exists");
        }
        this.scene = new Scene(face);
    }
    
    /**
     * Initializes the scene for the 1:1 comparison. Can be called only once.
     * 
     * @param primary Primary face
     * @param secondary Secondary face
     * @throws UnsupportedOperationException the the scene is already initiated
     * @throws IllegalArgumentException if some face is missing
     */
    public void initScene(HumanFace primary, HumanFace secondary) {
        if (scene != null) {
            throw new UnsupportedOperationException("Scene already exists");
        }
        this.scene = new Scene(primary, secondary);
    }
   
    /**
     * Returns the scene
     * @return the scene
     */
    public SceneRenderer getSceneRenderer() {
        return sceneRenderer;
    }
    
    /**
     * Renders the scene.
     */
    public void renderScene() {
        glCanvas.display();
    }
    
    /**
     * Returns the scene. 
     * 
     * @return the scene.
     */
    public Scene getScene() {
        return scene;
    }
    
    /**
     * Returns camera. 
     * 
     * @return camera
     */
    public Camera getCamera() {
        return camera;
    }
    
    /**
     * Returns the underlying OpenGL canvas.
     * 
     * @return the underlying OpenGL canvas.
     */
    public GLCanvas getGLCanvas() {
        return this.glCanvas;
    }

    
    /**
     * Sets background color
     * 
     * @param dark If {@code true}, then dark background is set
     */
    public void setDarkBackground(boolean dark) {
        if (dark) {
            canvasPanel.setBackground(SceneRenderer.DARK_BACKGROUND);
            sceneRenderer.setDarkBackground();
        } else {
            canvasPanel.setBackground(SceneRenderer.BRIGHT_BACKGROUND);
            sceneRenderer.setBrightBackground();
        }
    }
    
    private ControlButtons getButtonsPanel(Canvas canvas) {
        ControlButtonsAction controlButtonsListener = new ControlButtonsAction(canvas);
        ControlButtons ret = new ControlButtons(controlButtonsListener);
        ret.setOpaque(false);
        ret.setBounds(20, 20, ControlButtons.SIZE.width, ControlButtons.SIZE.height);
        return ret;
    }
    
    private JLayeredPane getLayeredPane() {
        JLayeredPane pane = new JLayeredPane();
        pane.addComponentListener(new java.awt.event.ComponentAdapter() {
            @Override
            public void componentResized(java.awt.event.ComponentEvent evt) {
                canvasPanel.setBounds(0, 0, layeredPane.getWidth(), layeredPane.getHeight());
                glCanvas.setBounds(layeredPane.getX(), layeredPane.getY(), layeredPane.getWidth(), layeredPane.getHeight());
            }
            @Override
            public void componentShown(java.awt.event.ComponentEvent evt) {
                canvasPanel.setBounds(0, 0, layeredPane.getWidth(), layeredPane.getHeight());
                glCanvas.setBounds(layeredPane.getX(), layeredPane.getY(), layeredPane.getWidth(), layeredPane.getHeight());
            }
        });
        return pane;
    }
    
    private JPanel getCanvasPanel() {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        panel.setBounds(0, 0, 0, 0);
        return panel;
    }
    
    private void initGLCanvas() {
        // gl version 2 is used
        GLCapabilities capabilities = new GLCapabilities(GLProfile.get(GLProfile.GL2));
        capabilities.setDoubleBuffered(true);
       
        // creates new glCanvas panel for displaying model
        glCanvas = new GLCanvas(capabilities);
        glCanvas.setVisible(true);     
        
        // enables glCanvas to react to events
        glCanvas.requestFocusInWindow();        
        glCanvas.setSize(getWidth() - getInsets().left - getInsets().right, getHeight() - getInsets().top - getInsets().bottom);

        glCanvas.addGLEventListener(listener);

        glCanvas.addMouseListener(manipulator);
        glCanvas.addMouseMotionListener(manipulator);
        glCanvas.addMouseWheelListener(manipulator);        
    }
    
}
