#define GL_GLEXT_PROTOTYPES
#include <GLES/gl.h>
#include <GLES/glext.h>
#include <GLES3/gl3.h>
#include <stdio.h>
#include <emscripten.h>
#include <emscripten/html5.h>

#define GL_CALL( x ) \
    { \
        x; \
        GLenum error = glGetError(); \
        if( error != GL_NO_ERROR ) \
            printf( "GL ERROR: %d,  %s\n", (int)error, #x ); \
    } \


    bool IsFramebufferValid( GLenum target )
    {
        // get framebuffer status from opengl
        GLenum status = glCheckFramebufferStatus( target );
        
        // debug output of the framebuffer status
        switch( status )
        {
            case GL_FRAMEBUFFER_COMPLETE:
                printf("****  Framebuffer COMPLETE\n");
                break;
            case GL_FRAMEBUFFER_UNDEFINED:
                printf("****  Target is the default framebuffer, but the default framebuffer does not exist.\n");
                break;
            case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
                printf("****  Any of the framebuffer attachment points are framebuffer incomplete.\n");
                break;
            case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
                printf("****  The framebuffer does not have at least one image attached to it.\n");
                break;
            case GL_FRAMEBUFFER_UNSUPPORTED:
                printf("****  the combination of internal formats of the attached images violates an "
                                "implementation-dependent set of restrictions.\n");
                break;
            case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
                printf("****  The value of GL_RENDERBUFFER_SAMPLES is not the same for all attached renderbuffers; "
                                "if the value of GL_TEXTURE_SAMPLES is the not same for all attached textures; or, if "
                                "the attached images are a mix of renderbuffers and textures, the value of "
                                "GL_RENDERBUFFER_SAMPLES does not match the value of GL_TEXTURE_SAMPLES."
                                "Or the value of GL_TEXTURE_FIXED_SAMPLE_LOCATIONS is not the same for all attached "
                                "textures; or, if the attached images are a mix of renderbuffers and textures, the "
                                "value of GL_TEXTURE_FIXED_SAMPLE_LOCATIONS is not GL_TRUE for all attached textures.\n");
                break;
            case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
                printf("****  GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS.\n");
                break;
            default:
                printf("****  IsFramebufferValid()  Unknown validation error: %#08x\n", (uint32_t)status );
                break;
        }
        
        return status == GL_FRAMEBUFFER_COMPLETE;
    }


int main()
{
    emscripten_set_canvas_size( 100, 100 );

    EmscriptenWebGLContextAttributes attrs;
    emscripten_webgl_init_context_attributes(&attrs);

    attrs.enableExtensionsByDefault = 1;
    attrs.majorVersion = 2;
    attrs.minorVersion = 0;

    EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context = emscripten_webgl_create_context( 0, &attrs );
    if (!context)
    {
        attrs.majorVersion = 1;
        context = emscripten_webgl_create_context( 0, &attrs );
        if (context) printf("Skipping test: WebGL 2.0 is not available.\n");
        else printf("Test failed: WebGL is not available!\n");
#ifdef REPORT_RESULT
        // We did not have WebGL 2, but were able to init WebGL 1? In that case, gracefully skip this test with the current browser not supporting this one.
        int result = context ? 0 : 12365;
        REPORT_RESULT(result);
#endif
        return 0;
    }
    emscripten_webgl_make_context_current(context);


    // Textures
    //
    int mips = 10;
    int sizeO = 512;

    unsigned short* data = new unsigned short[ 512 * 512 * 4 ];
    //memset( data, 0, 512 * 512 * 4 );

    // Create texture 1
    GLuint tex1;
    GL_CALL( glGenTextures( 1, &tex1 ) );
    GL_CALL( glBindTexture( GL_TEXTURE_CUBE_MAP, tex1 ) );
    for( int i=0; i<6; ++i )
    {
        GL_CALL( glTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, 0, GL_RGBA16F, sizeO, sizeO, 0, GL_RGBA, GL_HALF_FLOAT, NULL ) );
    }
    GL_CALL( glGenerateMipmap( GL_TEXTURE_CUBE_MAP ) );

    for( int i=0; i<6; ++i )
        GL_CALL( glTexSubImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, 0, 0, 0, sizeO, sizeO, GL_RGBA, GL_HALF_FLOAT, data ) );

    delete [] data;

    // Create texture 2
    GLuint tex2;
    GL_CALL( glGenTextures( 1, &tex2 ) );
    GL_CALL( glBindTexture( GL_TEXTURE_CUBE_MAP, tex2 ) );
    for( int i=0; i<6; ++i )
    {
        GL_CALL( glTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, 0, GL_RGBA16F, sizeO, sizeO, 0, GL_RGBA, GL_HALF_FLOAT, NULL ) );
    }
    GL_CALL( glGenerateMipmap( GL_TEXTURE_CUBE_MAP ) );



    // FBOs
    //

    // Create FBO 1
    GLuint fbo1;
    GL_CALL( glGenFramebuffers( 1, &fbo1 ) );
    GL_CALL( glBindFramebuffer( GL_FRAMEBUFFER, fbo1 ) );
    for( int level=0; level<mips; ++level )
    {
        for( int i=0; i<6; ++i )
        {
            GL_CALL( glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, tex1, level ) );
        }
    }
    IsFramebufferValid( GL_FRAMEBUFFER );

    // Create FBO 2
    GLuint fbo2;
    GL_CALL( glGenFramebuffers( 1, &fbo2 ) );
    GL_CALL( glBindFramebuffer( GL_FRAMEBUFFER, fbo2 ) );
    for( int level=0; level<mips; ++level )
    {
        for( int i=0; i<6; ++i )
        {
            GL_CALL( glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, tex2, level ) );

            glViewport( 0, 0, sizeO, sizeO );
        }
    }
    IsFramebufferValid( GL_FRAMEBUFFER );


    // Copy FBO 1 to 2
    int nw = sizeO;
    GL_CALL( glBindFramebuffer( GL_READ_FRAMEBUFFER, fbo1 ) );
    GL_CALL( glBindFramebuffer( GL_DRAW_FRAMEBUFFER, fbo2 ) );
    for( int level=0; level<mips; ++level )
    {
        for( int i=0; i<6; ++i )
        {
            GL_CALL( glFramebufferTexture2D( GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, tex1, level ) );
            GL_CALL( glFramebufferTexture2D( GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, tex2, level ) );

            glViewport( 0, 0, nw, nw );

            GL_CALL( glBlitFramebuffer( 0, 0, nw, nw, 0, 0, nw, nw, GL_COLOR_BUFFER_BIT, GL_NEAREST ) );
        }

        nw /= 2;
    }

    GL_CALL( glBindFramebuffer( GL_READ_FRAMEBUFFER, 0 ) );
    GL_CALL( glBindFramebuffer( GL_DRAW_FRAMEBUFFER, 0 ) );  

#ifdef REPORT_RESULT
    REPORT_RESULT(0);
#endif

  return 0;
}
