/**
 * 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.generic;

import jogamp.opengl.util.stereo.DistortionMesh;
import jogamp.opengl.util.stereo.GenericStereoDevice;

import com.jogamp.common.util.ReflectionUtil;
import com.jogamp.math.FloatUtil;
import com.jogamp.math.FovHVHalves;
import com.jogamp.math.Vec3f;
import com.jogamp.nativewindow.util.Dimension;
import com.jogamp.nativewindow.util.DimensionImmutable;
import com.jogamp.opengl.util.stereo.EyeParameter;
import com.jogamp.opengl.util.stereo.StereoDevice;
import com.jogamp.opengl.util.stereo.StereoDeviceConfig;
import com.jogamp.opengl.util.stereo.StereoDeviceFactory;
import com.jogamp.opengl.util.stereo.StereoDeviceRenderer;
import com.jogamp.opengl.util.stereo.StereoUtil;

public class GenericStereoDeviceFactory extends StereoDeviceFactory {

    /**
     * Create a generic monoscopic {@link GenericStereoDeviceConfig generic device config}.
     * @param name
     * @param surfaceSizeInPixel
     * @param screenSizeInMeters
     * @param defaultEyePositionOffset
     */
    public static GenericStereoDeviceConfig createMono(final String name,
                             final DimensionImmutable surfaceSizeInPixel, final float[] screenSizeInMeters,
                             final Vec3f defaultEyePositionOffset) {
        final float pupilCenterFromScreenTopInMeters = screenSizeInMeters[1] / 2f;
        final float d2r = FloatUtil.PI / 180.0f;
        return new GenericStereoDeviceConfig(
                        name,
                        GenericStereoDeviceConfig.ShutterType.RollingTopToBottom,
                        surfaceSizeInPixel,                // resolution
                        screenSizeInMeters,                // screenSize [m]
                        new DimensionImmutable[] { surfaceSizeInPixel }, // eye textureSize
                        pupilCenterFromScreenTopInMeters,  // pupilCenterFromScreenTop [m]
                        0f,                                // IPD [m]
                        new int[] { 0 },                   // eye order
                        new EyeParameter[] {
                            new EyeParameter(0, defaultEyePositionOffset,
                                             // degrees: 45/2 l, 45/2 r, 45/2 * aspect t, 45/2 * aspect b
                                             FovHVHalves.byFovyRadianAndAspect(45f*d2r, 1280f / 800f),
                                             0f /* distNoseToPupil */, 0f /* verticalDelta */, 0f /* eyeReliefInMeters */) },
                        0,      // supported sensor bits
                        null, // mash producer distortion bits
                        0,    // supported distortion bits
                        0,    // recommended distortion bits
                        0     // minimum distortion bits
                        );
    }

    /**
     * Create a generic homogenous side-by-side stereoscopic {@link GenericStereoDeviceConfig generic device config}.
     * @param name
     * @param surfaceSizeInPixel
     * @param screenSizeInMeters
     * @param interpupillaryDistanceInMeters
     * @param fovy
     * @param defaultEyePositionOffset
     */
    public static GenericStereoDeviceConfig createStereoSBS(final String name,
                             final DimensionImmutable surfaceSizeInPixel, final float[] screenSizeInMeters,
                             final float interpupillaryDistanceInMeters, final float fovy,
                             final Vec3f defaultEyePositionOffset) {
        final float pupilCenterFromScreenTopInMeters = screenSizeInMeters[1] / 2f;
        final float d2r = FloatUtil.PI / 180.0f;

        final DimensionImmutable eyeTextureSize = new Dimension(surfaceSizeInPixel.getWidth()/2, surfaceSizeInPixel.getHeight());
        final float[] horizPupilCenterFromLeft = StereoUtil.getHorizPupilCenterFromLeft(screenSizeInMeters[0], interpupillaryDistanceInMeters);
        final float vertPupilCenterFromTop = StereoUtil.getVertPupilCenterFromTop(screenSizeInMeters[1], pupilCenterFromScreenTopInMeters);
        final float aspect = (float)eyeTextureSize.getWidth() / (float)eyeTextureSize.getHeight();
        final FovHVHalves defaultSBSEyeFovLeft = FovHVHalves.byFovyRadianAndAspect(fovy * d2r, vertPupilCenterFromTop, aspect, horizPupilCenterFromLeft[0]);
        final FovHVHalves defaultSBSEyeFovRight = FovHVHalves.byFovyRadianAndAspect(fovy * d2r, vertPupilCenterFromTop, aspect, horizPupilCenterFromLeft[1]);

        return new GenericStereoDeviceConfig(
                        name,
                        GenericStereoDeviceConfig.ShutterType.RollingTopToBottom,
                        surfaceSizeInPixel,                // resolution
                        screenSizeInMeters,                // screenSize [m]
                        new DimensionImmutable[] { eyeTextureSize, eyeTextureSize }, // eye textureSize
                        pupilCenterFromScreenTopInMeters,  // pupilCenterFromScreenTop [m]
                        interpupillaryDistanceInMeters,    // IPD [m]
                        new int[] { 0, 1 },                // eye order
                        new EyeParameter[] {
                            new EyeParameter(0, defaultEyePositionOffset, defaultSBSEyeFovLeft,
                                          interpupillaryDistanceInMeters/2f /* distNoseToPupil */, 0f /* verticalDelta */, 0.010f /* eyeReliefInMeters */),
                            new EyeParameter(1, defaultEyePositionOffset, defaultSBSEyeFovRight,
                                         -interpupillaryDistanceInMeters/2f /* distNoseToPupil */, 0f /* verticalDelta */, 0.010f /* eyeReliefInMeters */) },
                        0,      // supported sensor bits
                        null,   // mash producer distortion bits
                        0,      // supported distortion bits
                        0,      // recommended distortion bits
                        0       // minimum distortion bits
                        );

    }

    /**
     * Create a generic lense distorted side-by-side stereoscopic {@link GenericStereoDeviceConfig generic device config}.
     * @param name
     * @param surfaceSizeInPixel
     * @param screenSizeInMeters
     * @param interpupillaryDistanceInMeters
     * @param fovy
     * @param eyeTextureSize
     * @param defaultEyePositionOffset
     */
    public static GenericStereoDeviceConfig createStereoSBSLense(final String name,
                             final DimensionImmutable surfaceSizeInPixel, final float[] screenSizeInMeters,
                             final float interpupillaryDistanceInMeters, final float fovy,
                             final DimensionImmutable eyeTextureSize,
                             final Vec3f defaultEyePositionOffset) {
        DistortionMesh.Producer lenseDistMeshProduce = null;
        try {
            lenseDistMeshProduce =
                (DistortionMesh.Producer)
                ReflectionUtil.createInstance("jogamp.opengl.oculusvr.stereo.lense.DistortionMeshProducer", GenericStereoDevice.class.getClassLoader());
        } catch (final Throwable t) {
            if(StereoDevice.DEBUG) { System.err.println("Caught: "+t.getMessage()); t.printStackTrace(); }
        }
        if( null == lenseDistMeshProduce ) {
            return null;
        }
        final float pupilCenterFromScreenTopInMeters = screenSizeInMeters[1] / 2f;
        final float d2r = FloatUtil.PI / 180.0f;

        final float[] horizPupilCenterFromLeft = StereoUtil.getHorizPupilCenterFromLeft(screenSizeInMeters[0], interpupillaryDistanceInMeters);
        final float vertPupilCenterFromTop = StereoUtil.getVertPupilCenterFromTop(screenSizeInMeters[1], pupilCenterFromScreenTopInMeters);
        final float aspect = (float)eyeTextureSize.getWidth() / (float)eyeTextureSize.getHeight();
        final FovHVHalves defaultSBSEyeFovLeft = FovHVHalves.byFovyRadianAndAspect(fovy * d2r, vertPupilCenterFromTop, aspect, horizPupilCenterFromLeft[0]);
        final FovHVHalves defaultSBSEyeFovRight = FovHVHalves.byFovyRadianAndAspect(fovy * d2r, vertPupilCenterFromTop, aspect, horizPupilCenterFromLeft[1]);

        return new GenericStereoDeviceConfig(
                        name,
                        GenericStereoDeviceConfig.ShutterType.RollingTopToBottom,
                        surfaceSizeInPixel,                // resolution
                        screenSizeInMeters,                // screenSize [m]
                        new DimensionImmutable[] { eyeTextureSize, eyeTextureSize }, // eye textureSize
                        pupilCenterFromScreenTopInMeters,  // pupilCenterFromScreenTop [m]
                        interpupillaryDistanceInMeters,    // IPD [m]
                        new int[] { 0, 1 },                // eye order
                        new EyeParameter[] {
                            new EyeParameter(0, defaultEyePositionOffset, defaultSBSEyeFovLeft,
                                          interpupillaryDistanceInMeters/2f /* distNoseToPupil */, 0f /* verticalDelta */, 0.010f /* eyeReliefInMeters */),
                            new EyeParameter(1, defaultEyePositionOffset, defaultSBSEyeFovRight,
                                         -interpupillaryDistanceInMeters/2f /* distNoseToPupil */, 0f /* verticalDelta */, 0.010f /* eyeReliefInMeters */) },
                        0,      // supported sensor bits
                        lenseDistMeshProduce,
                        // supported distortion bits
                        StereoDeviceRenderer.DISTORTION_BARREL | StereoDeviceRenderer.DISTORTION_CHROMATIC | StereoDeviceRenderer.DISTORTION_VIGNETTE,
                        // recommended distortion bits
                        StereoDeviceRenderer.DISTORTION_BARREL | StereoDeviceRenderer.DISTORTION_CHROMATIC | StereoDeviceRenderer.DISTORTION_VIGNETTE,
                        // minimum distortion bits
                        StereoDeviceRenderer.DISTORTION_BARREL
                        );
    }


    public static boolean isAvailable() {
        return true;
    }

    @Override
    protected final StereoDevice createDeviceImpl(final int deviceIndex, final StereoDeviceConfig config, final boolean verbose) {
        return new GenericStereoDevice(this, deviceIndex, config);
    }

    private boolean isValid = true;

    @Override
    public boolean isValid() {
        return isValid;
    }

    @Override
    public final void shutdown() {
        if( isValid ) {
            // NOP
            isValid = false;
        }
    }
}
