/**
 * Copyright 2014 JogAmp Community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and should not be interpreted as representing official policies, either expressed
 * or implied, of JogAmp Community.
 */
package com.jogamp.opengl.util.stereo;

import com.jogamp.nativewindow.util.DimensionImmutable;
import com.jogamp.nativewindow.util.RectangleImmutable;
import com.jogamp.opengl.GL;

import com.jogamp.opengl.math.FovHVHalves;

/**
 * Stereoscopic device rendering interface.
 * <p>
 * The following pseudo-code describes how to implement a renderer
 * using a {@link StereoDeviceRenderer}.
 * See {@link StereoClientRenderer} which implements the following:
 * <ul>
 *   <li>device.{@link #beginFrame(GL)}</li>
 *   <li>For both eyes:<ul>
 *     <li>device.{@link #updateViewerPose(int)}</li>
 *     <li>if device.{@link #ppAvailable()}: Set the render target, e.g. FBO</li>
 *     <li>Set the viewport using {@link Eye#getViewport()}</li>
 *     <li>{@link StereoGLEventListener#reshapeForEye(com.jogamp.opengl.GLAutoDrawable, int, int, int, int, EyeParameter, ViewerPose) upstream.reshapeEye(..)}</li>
 *     <li>{@link StereoGLEventListener#display(com.jogamp.opengl.GLAutoDrawable, int) upstream.display(..)}.</li>
 *   </ul></li>
 *   <li>Reset the viewport</li>
 *   <li>If device.{@link #ppAvailable()}:<ul>
 *     <li>device.{@link #ppBegin(GL)}</li>
 *     <li>Use render target, e.g. FBO's texture</li>
 *     <li>device.{@link #ppBothEyes(GL)} or device.{@link #ppOneEye(GL, int)} for both eyes</li>
 *     <li>device.{@link #ppEnd(GL)}</li>
 *   </ul></li>
 *   <li>device.{@link #endFrame(GL)}</li>
 * </ul>
 * </p>
 * <a name="asymFOVRendering"><h5>Correct {@link FovHVHalves Asymmetric FOV} Rendering</h5></a>
 * <p>
 * The {@link StereoClientRenderer} shall render both images for each eye correctly <i>Off-axis</i>
 * utilizing an asymmetric camera frustum, i.e. by using {@link StereoDevice StereoDevice}'s {@link StereoDevice#getDefaultFOV() default} {@link FovHVHalves}.<br>
 *
 * Some references:
 * <ul>
 *   <li><a href="https://en.wikipedia.org/wiki/Binocular_vision">Wiki: Binocular Vision</a></li>
 *   <li><a href="http://paulbourke.net/stereographics/stereorender/">Paul Burke: Stereo Graphics - Stereo Renderer</a></li>
 *   <li><a href="https://en.wikipedia.org/wiki/Distortion_%28optics%29">Wiki: Distortion (Optics)</a></li>
 * </ul>
 * </p>
 */
public interface StereoDeviceRenderer {
    /**
     * Distortion Bit: Barrel distortion compensating lens pincushion distortion
     */
    public static final int DISTORTION_BARREL    = 1 << 0;

    /**
     * Distortion Bit: Chromatic distortion compensating lens chromatic aberration.
     */
    public static final int DISTORTION_CHROMATIC = 1 << 1;

    /**
     * Distortion Bit: Vignette distortion compensating lens chromatic aberration.
     */
    public static final int DISTORTION_VIGNETTE  = 1 << 2;

    /**
     * Distortion Bit: Timewarp distortion technique to predict
     *                 {@link ViewerPose} movement to reduce latency.
     * <p>
     * FIXME: Explanation needs refinement!
     * </p>
     */
    public static final int DISTORTION_TIMEWARP  = 1 << 3;


    /** Returns the {@link StereoDevice} of this {@link StereoDeviceRenderer} instance. */
    public StereoDevice getDevice();

    /**
     * Interface describing one eye of the stereoscopic device,
     * see {@link StereoDeviceRenderer#getEye(int)}.
     */
    public static interface Eye {
        /**
         * Returns the viewport for this eye.
         */
        public RectangleImmutable getViewport();
        /**
         * Returns the {@link EyeParameter} of this eye.
         */
        public EyeParameter getEyeParameter();
    }

    /**
     * Returns the {@link Eye} instance for the denoted <code>eyeNum</code>.
     */
    public Eye getEye(final int eyeNum);

    /**
     * Updates the {@link ViewerPose} and returns it.
     */
    public ViewerPose updateViewerPose();

    /**
     * Returns the last {@link ViewerPose}.
     */
    public ViewerPose getLastViewerPose();

    /**
     * Returns used distortion compensation bits, e.g. {@link #DISTORTION_BARREL},
     * in case the stereoscopic display requires such, i.e. in case lenses are utilized.
     * <p>
     * Distortion requires {@link #ppAvailable() post-processing}.
     * </p>
     */
    public int getDistortionBits();

    /**
     * Method returns <code>true</code> if using <i>side-by-side</i> (SBS)
     * stereoscopic images, otherwise <code>false</code>.
     * <p>
     * SBS requires that both eye's images are presented
     * <i>side-by-side</i> in the final framebuffer.
     * </p>
     * <p>
     * Either the renderer presents the images <i>side-by-side</i> according to the {@link Eye#getViewport() eye's viewport},
     * or {@link #ppAvailable() post-processing} is utilized to merge {@link #getTextureCount() textures}
     * to a <i>side-by-side</i> configuration.
     * </p>
     */
    public boolean usesSideBySideStereo();

    /**
     * Returns the surface size for each eye's a single image in pixel units.
     */
    public DimensionImmutable[] getEyeSurfaceSize();

    /**
     * Returns the total surface size required for the complete images in pixel units.
     * <p>
     * If {@link #usesSideBySideStereo()} the total size spans over both {@link #getEyeSurfaceSize()}, side-by-side.
     * </p>
     * <p>
     * Otherwise the size is equal to {@link #getEyeSurfaceSize()}.
     * </p>
     */
    public DimensionImmutable getTotalSurfaceSize();

    /**
     * Returns the used texture-image count for post-processing, see {@link #ppAvailable()}.
     * <p>
     * In case the renderer does not support multiple textures for post-processing,
     * or no post-processing at all, method returns zero despite the request
     * from {@link StereoDevice#createRenderer(int, int, float[], com.jogamp.opengl.math.FovHVHalves[], float)}.
     * </p>
     */
    public int getTextureCount();

    /** Returns the desired texture-image unit for post-processing, see {@link #ppAvailable()}. */
    public int getTextureUnit();

    /** Initialize OpenGL related resources */
    public void init(final GL gl);

    /** Release all OpenGL related resources */
    public void dispose(final GL gl);

    /** Notifying that a new frame is about to start. */
    public void beginFrame(final GL gl);

    /** Notifying that the frame has been rendered completely. */
    public void endFrame(final GL gl);

    /**
     * Returns <code>true</code> if stereoscopic post-processing is required and available,
     * otherwise <code>false</code>.
     * <p>
     * Stereoscopic post-processing is available if:
     * <ul>
     *   <li>one of the <i>distortion</i> bits are set, see {@link #getDistortionBits()}</li>
     * </ul>
     * </p>
     * <p>
     * If stereoscopic post-processing is used
     * the following post-processing methods must be called to before {@link #endFrame()}:
     * <ul>
     *   <li>{@link #ppBegin(GL)}</li>
     *   <li>{@link #ppOneEye(GL, int)} for both eyes</li>
     *   <li>{@link #ppEnd(GL)}</li>
     * </ul>
     * </p>
     */
    public boolean ppAvailable();

    /**
     * Begin stereoscopic post-processing, see {@link #ppAvailable()}.
     * <p>
     * {@link #updateViewerPose(int)} for both eyes must be called upfront
     * when rendering upstream {@link StereoGLEventListener}.
     * </p>
     *
     * @param gl
     */
    public void ppBegin(final GL gl);

    /**
     * Performs stereoscopic post-processing for one eye, see {@link #ppAvailable()}.
     * @param gl
     * @param eyeNum
     */
    public void ppOneEye(final GL gl, final int eyeNum);

    /**
     * End stereoscopic post-processing, see {@link #ppAvailable()}.
     * @param gl
     */
    public void ppEnd(final GL gl);

}