/* Test Evas GL EAPIs.
 *
 * This will try with opengl_x11 and buffer (OSMesa) and silently fail if
 * the engine or the GL library can't be initialized. This is to test Evas GL
 * APIs when they can actually work, ie. when OSMesa exists or when the engine
 * is GL.
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <stdio.h>

#ifdef _WIN32
# include <evil_private.h> /* dlopen */
#else
# include <dlfcn.h>
#endif

#define EFL_GFX_FILTER_BETA

#include <Evas.h>
#include <Evas_GL.h>
#include <Ecore_Evas.h>

#include "evas_suite.h"

static int
_detect_osmesa(void)
{
   /* assume that if libOSMesa.so links, then we can create an Evas GL */
   void *lib = dlopen("libOSMesa.so", RTLD_NOW);
   if (!lib)
     {
        printf("Could not find OSMesa! Skipping Evas GL tests.\n");
        return 0;
     }
   dlclose(lib);
   return 1;
}

#define START_EVASGL_TEST(engine, options) \
   Ecore_Evas *ee; Evas *evas; Evas_Object *im = NULL; \
   if (!strcmp(engine, "buffer") && !_detect_osmesa()) return; \
   putenv("EVAS_GL_API_DEBUG=1"); \
   if (!options || !strcmp(engine, "buffer")) ee = ecore_evas_new(engine, 0, 0, 1, 1, NULL); \
   else ee = ecore_evas_gl_x11_options_new(NULL, 0, 0, 0, 1, 1, options); \
   if (!ee) { printf("Could not create ecore evas. Skipping Evas GL tests.\n"); \
              goto init_failed; } \
   ecore_evas_show(ee); \
   ecore_evas_manual_render_set(ee, EINA_TRUE); \
   evas = ecore_evas_get(ee); \
   im = evas_object_image_filled_add(evas); \
   evas_object_geometry_set(im, 0, 0, 1, 1); \
   evas_object_show(im); \
   ecore_evas_manual_render(ee); \
   do {} while (0)

#define END_EVASGL_TEST() \
   init_failed: \
   evas_object_del(im); \
   ecore_evas_free(ee); \
   do {} while (0)

static void
_test_evasgl_init(const char *engine)
{
   START_EVASGL_TEST(engine, NULL);
   Evas_GL_Context *ctx;
   Evas_GL_Surface *sfc;
   Evas_GL_Config *cfg;
   Evas_GL_API *gl;
   Evas_GL *evgl;

   fail_if(!(evgl = evas_gl_new(evas)));
   fail_if(!(cfg = evas_gl_config_new()));
   fail_if(!(sfc = evas_gl_surface_create(evgl, cfg, 1, 1)));
   fail_if(!(ctx = evas_gl_context_create(evgl, NULL)));

   /* valid current states */
   fail_if(!evas_gl_make_current(evgl, sfc, ctx));
   fail_if(!evas_gl_make_current(evgl, NULL, NULL));

   /* no context but surface: invalid */
   fprintf(stderr, "================ IGNORE ERRORS BEGIN ================\n");
   fail_if(evas_gl_make_current(evgl, sfc, NULL) != EINA_FALSE);
   fprintf(stderr, "================  IGNORE ERRORS END  ================\n");

   /* API verification */
   fail_if(!(gl = evas_gl_api_get(evgl)));

   fail_if(!evas_gl_make_current(evgl, NULL, NULL));
   evas_gl_context_destroy(evgl, ctx);
   evas_gl_surface_destroy(evgl, sfc);
   evas_gl_config_free(cfg);
   evas_gl_free(evgl);

   END_EVASGL_TEST();
}

static void
_test_evasgl_current_get(const char *engine)
{
   START_EVASGL_TEST(engine, NULL);
   Evas_GL_Context *ctx, *ctx2;
   Evas_GL_Surface *sfc, *sfc2;
   Evas_GL_Config *cfg;
   Evas_GL *evgl, *evgl2;

   ctx2 = (void*)(intptr_t)0x1;
   sfc2 = (void*)(intptr_t)0x1;
   evgl2 = evas_gl_current_evas_gl_get(&ctx2, &sfc2);
   fail_if(evgl2 || ctx2 || sfc2);

   fail_if(!(evgl = evas_gl_new(evas)));
   fail_if(!(cfg = evas_gl_config_new()));
   fail_if(!(sfc = evas_gl_surface_create(evgl, cfg, 1, 1)));
   fail_if(!(ctx = evas_gl_context_create(evgl, NULL)));

   ctx2 = (void*)(intptr_t)0x1;
   sfc2 = (void*)(intptr_t)0x1;
   evgl2 = evas_gl_current_evas_gl_get(&ctx2, &sfc2);
   fail_if(evgl2 || ctx2 || sfc2);

   fail_if(!evas_gl_make_current(evgl, sfc, ctx));
   evgl2 = evas_gl_current_evas_gl_get(&ctx2, &sfc2);
   fail_if(evgl2 != evgl);
   fail_if(sfc2 != sfc);
   fail_if(ctx2 != ctx);
   fail_if(evas_gl_current_surface_get(evgl) != sfc);
   fail_if(evas_gl_current_context_get(evgl) != ctx);

   fail_if(!evas_gl_make_current(evgl, NULL, NULL));
   evgl2 = evas_gl_current_evas_gl_get(&ctx2, &sfc2);
   fail_if(evgl2 != evgl); /* this should not reset current Evas GL */
   fail_if(sfc2 != NULL);
   fail_if(ctx2 != NULL);
   fail_if(evas_gl_current_surface_get(evgl));
   fail_if(evas_gl_current_context_get(evgl));

   evas_gl_context_destroy(evgl, ctx);
   evas_gl_surface_destroy(evgl, sfc);
   evas_gl_free(evgl);
   END_EVASGL_TEST();
}

static void
_test_evasgl_context_version(const char *engine)
{
   START_EVASGL_TEST(engine, NULL);
   Evas_GL_Context *ctx, *ctx2;
   Evas_GL_Surface *sfc;
   Evas_GL_Config *cfg;
   Evas_GL_API *gl;
   Evas_GL *evgl;
   const char *ver, *ext, *glsl;
   int vmaj, vmin;

   fail_if(!(evgl = evas_gl_new(evas)));
   fail_if(!(cfg = evas_gl_config_new()));
   fail_if(!(sfc = evas_gl_surface_create(evgl, cfg, 1, 1)));

   /* test only GLES 2 - GLES 3 and GLES 1 not supported yet with OSMesa */
   fail_if(!(ctx = evas_gl_context_version_create(evgl, NULL, EVAS_GL_GLES_2_X)));
   fail_if(!(gl = evas_gl_api_get(evgl)));
   fail_if(gl->version != EVAS_GL_API_VERSION); /* valid for make check */

   fail_if(!evas_gl_make_current(evgl, sfc, ctx));
   fail_if(!(ver = (const char *) gl->glGetString(GL_VERSION)));
   printf("GL_VERSION: %s\n", ver);
   fail_if(sscanf(ver, "OpenGL ES %d.%d", &vmaj, &vmin) != 2);
   fail_if((vmaj < 2) || (vmin < 0));

   /* verify function pointers - GLES 2 only */
   typedef void (*fptr_t)();
   const int idx_glViewport = ((char*) &gl->glViewport - (char*) &gl->glActiveTexture) / sizeof(fptr_t);
   fptr_t *fptr= &gl->glActiveTexture;
   for (int i = 0; i < idx_glViewport; i++, fptr++)
     fail_if(!*fptr);

   /* mesa supports a ton of extensions but there are no extra pointers set in
    * Evas_GL_API for those extensions */
   fail_if(!(ext = (const char *) gl->glGetString(GL_EXTENSIONS)));
   printf("GL_EXTENSIONS: %s\n", ext);

   /* GLSL string format check */
   fail_if(!(glsl = (const char *) gl->glGetString(GL_SHADING_LANGUAGE_VERSION)));
   printf("GL_SHADING_LANGUAGE_VERSION: %s\n", glsl);
   fail_if(sscanf(glsl, "OpenGL ES GLSL ES %d", &vmaj) != 1);

   /* shared ctx */
   fail_if(!(ctx2 = evas_gl_context_version_create(evgl, ctx, EVAS_GL_GLES_2_X)));
   evas_gl_context_destroy(evgl, ctx2);

   fail_if(!evas_gl_make_current(evgl, NULL, NULL));
   evas_gl_context_destroy(evgl, ctx);
   evas_gl_surface_destroy(evgl, sfc);
   evas_gl_free(evgl);
   END_EVASGL_TEST();
}

static void
_test_evasgl_surfaceless_context(const char *engine)
{
   START_EVASGL_TEST(engine, NULL);
   Evas_GL_Context *ctx;
   Evas_GL_Surface *sfc;
   Evas_GL_Config *cfg;
   Evas_GL_API *gl;
   Evas_GL *evgl;
   const char *eexts;

   fail_if(!(evgl = evas_gl_new(evas)));
   fail_if(!(ctx = evas_gl_context_create(evgl, NULL)));

   // FIXME: evas_gl_string_query will fail before the first make_current (GL)
   fail_if(!(cfg = evas_gl_config_new()));
   fail_if(!(sfc = evas_gl_surface_create(evgl, cfg, 1, 1)));
   fail_if(!evas_gl_make_current(evgl, sfc, ctx));
   // FIXME

   fail_if(!(gl = evas_gl_context_api_get(evgl, ctx)));

   eexts = evas_gl_string_query(evgl, EVAS_GL_EXTENSIONS);
   if (eexts && strstr(eexts, "EGL_KHR_surfaceless_context"))
     {
        fail_if(!evas_gl_make_current(evgl, NULL, ctx));
        fail_if(!gl->glGetString(GL_VERSION));
     }
   else printf("Surfaceless context not supported. Skipped.\n");

   fail_if(!evas_gl_make_current(evgl, NULL, NULL));
   evas_gl_context_destroy(evgl, ctx);
   evas_gl_surface_destroy(evgl, sfc);
   evas_gl_config_free(cfg);
   evas_gl_free(evgl);
   END_EVASGL_TEST();
}

static void
_test_evasgl_glreadpixels(const char *engine)
{
   /* simple test verifying surface render works as expected */

   START_EVASGL_TEST(engine, NULL);
   Evas_GL_Context *ctx;
   Evas_GL_Surface *sfc;
   Evas_GL_Config *cfg;
   Evas_GL_API *gl;
   Evas_GL *evgl;
   unsigned int pixel;

   fail_if(!(evgl = evas_gl_new(evas)));
   fail_if(!(cfg = evas_gl_config_new()));
   fail_if(!(ctx = evas_gl_context_create(evgl, NULL)));
   fail_if(!(sfc = evas_gl_surface_create(evgl, cfg, 1, 1)));
   fail_if(!evas_gl_make_current(evgl, sfc, ctx));
   fail_if(!(gl = evas_gl_context_api_get(evgl, ctx)));

   gl->glClearColor(0, 1, 0, 1);
   gl->glClear(GL_COLOR_BUFFER_BIT);
   gl->glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &pixel);
   fail_if(gl->glGetError() != GL_NO_ERROR);
   fail_if(pixel != 0xFF00FF00);

   fail_if(!evas_gl_make_current(evgl, NULL, NULL));
   evas_gl_context_destroy(evgl, ctx);
   evas_gl_surface_destroy(evgl, sfc);
   evas_gl_config_free(cfg);
   evas_gl_free(evgl);
   END_EVASGL_TEST();
}

static void
_test_evasgl_fbo(const char *engine)
{
   /* simple test verifying FBO render works as expected */

   START_EVASGL_TEST(engine, NULL);
   Evas_GL_Context *ctx;
   Evas_GL_Surface *sfc;
   Evas_GL_Config *cfg;
   Evas_GL_API *gl;
   Evas_GL *evgl;
   unsigned int pixel;
   GLuint fbo, tex, vtx, frg, prg, u;
   GLint status;

   static const char *vertex =
         "#ifdef GL_ES\n"
         "precision mediump float;\n"
         "#endif\n"
         "attribute vec4 vertex;\n"
         "void main()\n"
         "{\n"
         "   gl_Position = vertex;\n"
         "}\n";
   static const char *fragment =
         "#ifdef GL_ES\n"
         "precision mediump float;\n"
         "#endif\n"
         "uniform vec4 color;\n"
         "void main()\n"
         "{\n"
         "   gl_FragColor = color;\n"
         "}\n";
   static const GLfloat color[] = { 1, 0, 0, 1 };
   static const GLfloat vertices[] = {
       1.0,  1.0,  0.0,
      -1.0, -1.0,  0.0,
       1.0, -1.0,  0.0,
       1.0,  1.0,  0.0,
      -1.0,  1.0,  0.0,
      -1.0, -1.0,  0.0
   };

   /* surface & context */
   fail_if(!(evgl = evas_gl_new(evas)));
   fail_if(!(cfg = evas_gl_config_new()));
   cfg->color_format = EVAS_GL_RGBA_8888;
   fail_if(!(ctx = evas_gl_context_create(evgl, NULL)));
   fail_if(!(sfc = evas_gl_surface_create(evgl, cfg, 1, 1)));
   fail_if(!evas_gl_make_current(evgl, sfc, ctx));
   fail_if(!(gl = evas_gl_context_api_get(evgl, ctx)));

   /* generate fbo */
   gl->glGenFramebuffers(1, &fbo);
   gl->glGenTextures(1, &tex);
   gl->glBindFramebuffer(GL_FRAMEBUFFER, fbo);
   gl->glBindTexture(GL_TEXTURE_2D, tex);
   gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
   gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
   gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
   gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
   gl->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
   gl->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
   fail_if(gl->glGetError() != GL_NO_ERROR);

   /* prepare program */
   vtx = gl->glCreateShader(GL_VERTEX_SHADER);
   gl->glShaderSource(vtx, 1, &vertex, NULL);
   gl->glCompileShader(vtx);
   gl->glGetShaderiv(vtx, GL_COMPILE_STATUS, &status);
   fail_if((gl->glGetError() != GL_NO_ERROR) || !status);
   frg = gl->glCreateShader(GL_FRAGMENT_SHADER);
   gl->glShaderSource(frg, 1, &fragment, NULL);
   gl->glCompileShader(frg);
   gl->glGetShaderiv(frg, GL_COMPILE_STATUS, &status);
   fail_if((gl->glGetError() != GL_NO_ERROR) || !status);
   prg = gl->glCreateProgram();
   gl->glAttachShader(prg, vtx);
   gl->glAttachShader(prg, frg);
   gl->glBindAttribLocation(prg, 0, "vertex");
   gl->glLinkProgram(prg);
   gl->glGetProgramiv(prg, GL_LINK_STATUS, &status);
   fail_if((gl->glGetError() != GL_NO_ERROR) || !status);

   /* clear */
   gl->glClearColor(0, 0, 0, 0);
   gl->glClear(GL_COLOR_BUFFER_BIT);
   fail_if(gl->glGetError() != GL_NO_ERROR);

   /* set parameters */
   gl->glUseProgram(prg);
   u = gl->glGetUniformLocation(prg, "color");
   gl->glUniform4fv(u, 1, color);
   gl->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertices);
   gl->glEnableVertexAttribArray(0);
   fail_if(gl->glGetError() != GL_NO_ERROR);

   /* draw */
   gl->glDrawArrays(GL_TRIANGLES, 0, 6);
   gl->glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &pixel);
   fail_if(gl->glGetError() != GL_NO_ERROR);
   fail_if(pixel != 0xFF0000FF);

   fail_if(!evas_gl_make_current(evgl, NULL, NULL));
   evas_gl_context_destroy(evgl, ctx);
   evas_gl_surface_destroy(evgl, sfc);
   evas_gl_config_free(cfg);
   evas_gl_free(evgl);
   END_EVASGL_TEST();
}

static void
_test_evasgl_pbuffer(const char *engine)
{
   /* check support for PBuffer */

   // FIXME: No SW engine support!
   if (!strcmp(engine, "buffer"))
     {
        printf("PBuffer not supported (SW engine). Skipped.\n");
        return;
     }

   START_EVASGL_TEST(engine, NULL);
   Evas_GL_Context *ctx;
   Evas_GL_Surface *sfc;
   Evas_GL_Config *cfg;
   Evas_GL_API *gl;
   Evas_GL *evgl;
   unsigned int pixel;

   fail_if(!(evgl = evas_gl_new(evas)));
   fail_if(!(cfg = evas_gl_config_new()));
   cfg->color_format = EVAS_GL_RGBA_8888;
   fail_if(!(ctx = evas_gl_context_create(evgl, NULL)));
   fail_if(!(sfc = evas_gl_pbuffer_surface_create(evgl, cfg, 1, 1, NULL)));
   fail_if(!evas_gl_make_current(evgl, sfc, ctx));
   fail_if(!(gl = evas_gl_context_api_get(evgl, ctx)));

   /* clear */
   gl->glClearColor(0, 1, 0, 1);
   gl->glClear(GL_COLOR_BUFFER_BIT);
   gl->glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &pixel);
   fail_if(gl->glGetError() != GL_NO_ERROR);
   fail_if(pixel != 0xFF00FF00);

   fail_if(!evas_gl_make_current(evgl, NULL, NULL));
   evas_gl_context_destroy(evgl, ctx);
   evas_gl_surface_destroy(evgl, sfc);
   evas_gl_config_free(cfg);
   evas_gl_free(evgl);
   END_EVASGL_TEST();
}

static void
_test_evasgl_formats(const char *engine)
{
   /* check that Evas GL always falls back to working surface configs */

   const int options[] = { ECORE_EVAS_GL_X11_OPT_GL_DEPTH, 24,
                           ECORE_EVAS_GL_X11_OPT_GL_STENCIL, 8,
                           ECORE_EVAS_GL_X11_OPT_GL_MSAA, 4,
                           0 };
   const int *opts = options;

   START_EVASGL_TEST(engine, opts);
   Evas_GL_Context *ctx;
   Evas_GL_Surface *sfc;
   Evas_GL_Config *cfg;
   Evas_GL *evgl;

   fail_if(!(evgl = evas_gl_new(evas)));
   fail_if(!(cfg = evas_gl_config_new()));
   fail_if(!(ctx = evas_gl_context_create(evgl, NULL)));

   // don't test DEPTH_32 or STENCIL_16
   for (Evas_GL_Color_Format fmt = EVAS_GL_RGB_888; fmt < EVAS_GL_NO_FBO; fmt++)
     for (Evas_GL_Depth_Bits depth = EVAS_GL_DEPTH_NONE; depth <= EVAS_GL_DEPTH_BIT_24; depth++)
       for (Evas_GL_Stencil_Bits stencil = EVAS_GL_STENCIL_NONE; stencil <= EVAS_GL_STENCIL_BIT_8; stencil++)
         for (Evas_GL_Options_Bits opt = EVAS_GL_OPTIONS_NONE; opt <= 2; opt += 2)
           for (Evas_GL_Multisample_Bits msaa = EVAS_GL_MULTISAMPLE_NONE; msaa <= EVAS_GL_MULTISAMPLE_HIGH; msaa++)
             {
                cfg->color_format = fmt;
                cfg->depth_bits = depth;
                cfg->stencil_bits = stencil;
                cfg->options_bits = opt;
                cfg->multisample_bits = msaa;
                cfg->gles_version = EVAS_GL_GLES_2_X;
                /*
                fprintf(stderr, "Testing surface %d %d %d %d %d\n",
                        cfg->color_format, cfg->depth_bits, cfg->stencil_bits,
                        cfg->multisample_bits, cfg->options_bits);
                        */
                fail_if(!(sfc = evas_gl_surface_create(evgl, cfg, 1, 1)));
                fail_if(!evas_gl_make_current(evgl, sfc, ctx));
                fail_if(!evas_gl_make_current(evgl, NULL, NULL));
                evas_gl_surface_destroy(evgl, sfc);
             }

   fail_if(!evas_gl_make_current(evgl, NULL, NULL));
   evas_gl_context_destroy(evgl, ctx);
   evas_gl_config_free(cfg);
   evas_gl_free(evgl);
   END_EVASGL_TEST();
}

#define TEST_ADD_OPT(name, opt) \
   EFL_START_TEST(evas ## name ## _opengl_x11) \
   { name("opengl_x11"); } \
   EFL_END_TEST \
   EFL_START_TEST(evas ## name ## _buffer) \
   { name("buffer"); } \
   EFL_END_TEST
#define TEST_ADD(name) TEST_ADD_OPT(name, NULL)

TEST_ADD(_test_evasgl_init)
TEST_ADD(_test_evasgl_current_get)
TEST_ADD(_test_evasgl_context_version)
TEST_ADD(_test_evasgl_surfaceless_context)
TEST_ADD(_test_evasgl_glreadpixels)
TEST_ADD(_test_evasgl_fbo)
TEST_ADD(_test_evasgl_formats)
TEST_ADD(_test_evasgl_pbuffer)

/* APIs still to test:
 *
 * evas_gl_rotation_get
 * evas_gl_surface_query
 * evas_gl_error_get
 * evas_gl_native_surface_get
 * evas_gl_proc_address_get
 *
 * Most of these actually need support in SW engine to be improved.
 */

void evas_test_evasgl(TCase *tc)
{
#undef TEST_ADD
#define TEST_ADD(name) tcase_add_test(tc, evas ## name ## _opengl_x11);

   if (getenv("DISPLAY"))
     {
        TEST_ADD(_test_evasgl_init);
        TEST_ADD(_test_evasgl_current_get);
        TEST_ADD(_test_evasgl_context_version);
        TEST_ADD(_test_evasgl_surfaceless_context);
        TEST_ADD(_test_evasgl_glreadpixels);
        TEST_ADD(_test_evasgl_fbo);
        TEST_ADD(_test_evasgl_pbuffer);
        TEST_ADD(_test_evasgl_formats);
     }

#undef TEST_ADD
#define TEST_ADD(name) tcase_add_test(tc, evas ## name ## _buffer);

   TEST_ADD(_test_evasgl_init);
   TEST_ADD(_test_evasgl_current_get);
   TEST_ADD(_test_evasgl_context_version);
   TEST_ADD(_test_evasgl_surfaceless_context);
   TEST_ADD(_test_evasgl_glreadpixels);
   TEST_ADD(_test_evasgl_fbo);
   TEST_ADD(_test_evasgl_pbuffer);
   TEST_ADD(_test_evasgl_formats);
}
