/**
 * Copyright 2013 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.test.junit.jogl.acore;

import java.nio.FloatBuffer;
import java.nio.ShortBuffer;

import com.jogamp.common.nio.Buffers;
import com.jogamp.opengl.test.junit.util.MiscUtils;
import com.jogamp.opengl.test.junit.util.UITestCase;
import com.jogamp.opengl.util.GLBuffers;

import com.jogamp.opengl.GL;
import com.jogamp.opengl.GL2;
import com.jogamp.opengl.GL2ES2;
import com.jogamp.opengl.GLAutoDrawable;
import com.jogamp.opengl.GLCapabilities;
import com.jogamp.opengl.GLDrawableFactory;
import com.jogamp.opengl.GLEventListener;
import com.jogamp.opengl.GLException;
import com.jogamp.opengl.GLOffscreenAutoDrawable;
import com.jogamp.opengl.GLProfile;

import org.junit.Assert;
import org.junit.Test;
import org.junit.FixMethodOrder;
import org.junit.runners.MethodSorters;

/**
 * CPU sourced data API entry not allowed on ES3 and GL core >= 3.0.
 *
 * See https://jogamp.org/bugzilla/show_bug.cgi?id=852#c1
 */
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TestCPUSourcingAPINEWT extends UITestCase {
    static long duration = 500; // ms

    static class Demo implements GLEventListener {
        private final static float[] vertexColorData = new float[]{
             0.0f,  0.75f, 0.0f,  1,0,0,
            -0.5f, -0.75f, 0.0f,  0,1,0,
             0.9f, -0.75f, 0.0f,  0,0,1
        };
        private final FloatBuffer vertexColorDataBuffer = GLBuffers.newDirectFloatBuffer(vertexColorData);

        private final short[] indices = new short[]{0, 1, 2};
        private final ShortBuffer indicesBuffer = GLBuffers.newDirectShortBuffer(indices);


        private int vertID = -1;
        private int fragID = -1;
        private int progID = -1;

        private static int createShader(final GL2ES2 gl, final int type,
                final String[] srcLines){
            final int shaderID = gl.glCreateShader(type);
            assert shaderID > 0;
            final int[] lengths  = new int[srcLines.length];
            for (int i = 0; i < srcLines.length; i++) {
                lengths[i] = srcLines[i].length();
            }
            gl.glShaderSource(shaderID, srcLines.length, srcLines, lengths, 0);
            gl.glCompileShader(shaderID);
            return shaderID;
        }

        private void initShaders(final GL2ES2 gl) {
            final String[] vertSrc = new String[]{
                "#version 150\n",
                "in vec4 vPosition;\n",
                "in vec4 vColor;\n",
                "out vec4 pColor;\n",
                "void main() {\n",
                "    pColor       = vColor;\n",
                "    gl_Position = vPosition;\n",
                "}\n"
            };
            vertID = createShader(gl, GL2ES2.GL_VERTEX_SHADER, vertSrc);

            final String[] fragSrc = new String[]{
                "#version 150\n",
                "in vec4 pColor;\n",
                "void main() {\n",
                "    gl_FragColor = pColor;\n",
                "}\n"
            };
            fragID = createShader(gl, GL2ES2.GL_FRAGMENT_SHADER, fragSrc);

            // We're done with the compiler
            gl.glReleaseShaderCompiler();

            progID = gl.glCreateProgram();
            assert progID > 0;
            gl.glAttachShader(progID, vertID);
            gl.glAttachShader(progID, fragID);
            gl.glLinkProgram(progID);
            gl.glValidateProgram(progID);
        }

        @Override
        public void init(final GLAutoDrawable drawable) {
            final GL2ES2 gl = drawable.getGL().getGL2ES2();
            gl.glEnable(GL.GL_DEPTH_TEST);
            gl.glDisable(GL.GL_CULL_FACE);
            initShaders(gl);

            gl.setSwapInterval(1);
        }

        @Override
        public void dispose(final GLAutoDrawable drawable) {
            final GL2ES2 gl = drawable.getGL().getGL2ES2();
            gl.glDetachShader(progID, fragID);
            gl.glDetachShader(progID, vertID);
            gl.glDeleteProgram(progID);
            gl.glDeleteShader(fragID);
            gl.glDeleteShader(vertID);
        }

        private void displayCPUSourcing(final GL2 gl) {
            final int posLoc    = gl.glGetAttribLocation(progID, "vPosition");
            final int colorLoc = gl.glGetAttribLocation(progID, "vColor");
            gl.glEnableVertexAttribArray(posLoc);
            gl.glEnableVertexAttribArray(colorLoc);

            final int stride = 6 * Buffers.SIZEOF_FLOAT;
            // final int cOff   = 3 * Buffers.SIZEOF_FLOAT;
            gl.glVertexAttribPointer(posLoc,  3, GL.GL_FLOAT, false, stride, vertexColorDataBuffer);
            vertexColorDataBuffer.position(3); // move to cOff
            gl.glVertexAttribPointer(colorLoc,3, GL.GL_FLOAT, false, stride, vertexColorDataBuffer);
            vertexColorDataBuffer.position(0); // rewind cOff

            gl.glDrawElements(GL.GL_TRIANGLES, 3, GL.GL_UNSIGNED_SHORT, indicesBuffer);

            gl.glDisableVertexAttribArray(posLoc);
            gl.glDisableVertexAttribArray(colorLoc);
        }

        @Override
        public void display(final GLAutoDrawable drawable) {
            final GL2ES2 gl = drawable.getGL().getGL2ES2();
            gl.glClearColor(0x44, 0x44, 0x44, 0);
            gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
            gl.glUseProgram(progID);

            // Hard casting is of course invalid!
            // But we need to 'fake' compatibility mode to trigger CPU-sourcing w/ GL3 core
            displayCPUSourcing((GL2) gl);

            gl.glUseProgram(0);
        }

        @Override
        public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int w, final int h) {
        }
    }

    private void testImpl(final GLProfile profile) throws InterruptedException {
        final GLCapabilities capabilities = new GLCapabilities(profile);
        final GLDrawableFactory factory = GLDrawableFactory.getFactory(profile);
        final GLOffscreenAutoDrawable glad = factory.createOffscreenAutoDrawable(null, capabilities, null, 512, 512);

        final Demo vaoTest = new Demo();
        glad.addGLEventListener(vaoTest);
        glad.display();

        glad.destroy();
    }

    @Test
    public void test01GL2CPUSource() throws GLException, InterruptedException {
        if( ! GLProfile.isAvailable(GLProfile.GL2) ) {
            System.err.println("GL2 n/a");
            return;
        }
        testImpl(GLProfile.get(GLProfile.GL2));
    }

    @Test
    public void test02GL3CPUSource() throws GLException, InterruptedException {
        final GLProfile glp = GLProfile.getMaxProgrammableCore(true);
        if( !glp.isGL3ES3() && !glp.isGL2ES2() ) {
            System.err.println("No GL core profile available, got "+glp);
            return;
        }
        GLException exp = null;
        try {
            testImpl(glp);
        } catch(final GLException gle) {
            exp = gle;
            System.err.println("Expected Exception: "+exp.getMessage());
        }
        Assert.assertNotNull("Excpected GLException missing due to CPU Sourcing w/ GL3 core context", exp);
    }

    public static void main(final String args[]) {
        for(int i=0; i<args.length; i++) {
            if(args[i].equals("-time")) {
                i++;
                duration = MiscUtils.atol(args[i], duration);
            }
        }
        org.junit.runner.JUnitCore.main(TestCPUSourcingAPINEWT.class.getName());
    }
}
