/// omap3.cpp
///
/// (c) 2014 by Bastian Spiegel <bs@tkscript.de>
///     - Distributed under terms of the Lesser GNU General Public License (LGPL).
///       See COPYING and <http://www.gnu.org/licenses/licenses.html#LGPL> for further information.
///

///#define USE_ARGB32 defined

#include <stdio.h>
#include <stdlib.h>

#define YAC_PRINTF defined
#include <yac.h>

// #include <unistd.h>
// #include <sys/time.h>
// #include <sys/timerfd.h>
// #include <pthread.h>
// #include <errno.h>
// #include <syscall.h>

#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>

#include <linux/fb.h>
#include <linux/omapfb.h>


#include "HAL.h"

extern YAC_Host *yac_host;

#include "../tkopengl/inc_opengl.h"


#define Dcheckeglerror(s) if(YAC_TRUE) { GLenum err = ::eglGetError(); if(EGL_SUCCESS != err) printf("%s() returned EGL error %d (0x%08x) in file \"%s\", line %d.\n", #s, err, (sU32)err, __FILE__, __LINE__); } else (void)0

#define Dcheckglerror(s) if(YAC_TRUE) { GLenum err = ::glGetError(); if(0 != err) printf("%s() returned GL error %d (0x%08x) in file \"%s\", line %d.\n", #s, err, (sU32)err, __FILE__, __LINE__); } else (void)0


NativeDisplayType native_display;
NativeWindowType  native_window;

EGLDisplay egl_display = NULL;
EGLConfig  egl_config  = NULL;
EGLContext egl_context = NULL;

EGLSurface egl_window_surface = NULL;

static const EGLint egl_context_attribs[] = {
   EGL_CONTEXT_CLIENT_VERSION, 2,
   EGL_NONE, EGL_NONE
};


static const EGLint egl_window_surface_attribs[] = {
   EGL_RENDER_BUFFER, EGL_BACK_BUFFER,
   //EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER,

   EGL_NONE, EGL_NONE
};


static const EGLint egl_config_attribs[] = {
   EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,

#ifdef USE_ARGB32
   EGL_BUFFER_SIZE, 32,

   EGL_ALPHA_SIZE, 8,
   EGL_RED_SIZE,   8,
   EGL_GREEN_SIZE, 8,
   EGL_BLUE_SIZE,  8,

   EGL_BIND_TO_TEXTURE_RGBA, EGL_TRUE,
#else
   //EGL_BUFFER_SIZE, 16,

   //EGL_ALPHA_SIZE, 0,
   EGL_RED_SIZE,   5,
   EGL_GREEN_SIZE, 6,
   EGL_BLUE_SIZE,  5,

   //EGL_BIND_TO_TEXTURE_RGB,  EGL_TRUE,
   //EGL_BIND_TO_TEXTURE_RGBA, EGL_TRUE,
#endif /* USE_ARGB32 */


   EGL_STENCIL_SIZE, 8,
   EGL_DEPTH_SIZE, 8,

   //EGL_NATIVE_RENDERABLE, EGL_TRUE,
   //EGL_NATIVE_RENDERABLE, EGL_FALSE,

   //EGL_MIN_SWAP_INTERVAL, 1,

   EGL_NONE, EGL_NONE
};


static int fb_fd = -1;

// ---------------------------------------------------------------------------- loc_gles_query_print_int()
static void loc_gles_query_print_int(GLenum _type, const char *_msg) {
   char buf[1024];
   GLenum glErr;
   GLint val;

   /* (todo) check whether _msg contains format string (%d, %u, ..) */

   int l = strlen(_msg);

   if(l > 1000)
   {
      l = 1000;
   }
   
   strncpy(buf, _msg, 900);

   ::glGetIntegerv(_type, &val);

   glErr = ::glGetError();

   if(0 == glErr)
   {
      strncpy(buf + l, "%d\n", 123);

      printf(buf, val);
   }
   else
   {
      strncpy(buf + l, "<GL error 0x%08x>\n", 123);

      printf(buf, (sU32)glErr);
   }
}

// ---------------------------------------------------------------------------- loc_gles_query_print_int2()
static void loc_gles_query_print_int2(GLenum _type, const char *_msg) {
   char buf[1024];
   GLenum glErr;
   GLint val[2];

   /* (todo) check whether _msg contains format string (%d, %u, ..) */

   int l = strlen(_msg);

   if(l > 1000)
   {
      l = 1000;
   }
   
   strncpy(buf, _msg, 900);

   ::glGetIntegerv(_type, val);

   glErr = ::glGetError();

   if(0 == glErr)
   {
      strncpy(buf + l, "(%d; %d)\n", 123);

      printf(buf, val[0], val[1]);
   }
   else
   {
      strncpy(buf + l, "<GL error 0x%08x>\n", 123);

      printf(buf, (sU32)glErr);
   }
}

// ----------------------------------------------------------------------------- loc_gles_exit()
static void loc_gles_exit(void) {

   if(NULL != egl_display)
   {
      ::eglMakeCurrent(egl_display, NULL, NULL, EGL_NO_CONTEXT);
      Dcheckeglerror(eglMakeCurrent);

      if(NULL != egl_context)
      {
         ::eglDestroyContext(egl_display, egl_context);
         Dcheckeglerror(eglDestroyContext);

         egl_context = NULL;

         yac_host->printf("[dbg] omap3::gles_exit: eglDestroyContext() called\n");
      }

#ifdef USE_PBUFFER
      if(NULL != egl_pbuffer_surface)
      {
         ::eglDestroySurface(egl_display, egl_pbuffer_surface);
         Dcheckeglerror(eglDestroySurface);

         egl_pbuffer_surface = NULL;
      }
#endif

      if(NULL != egl_window_surface)
      {
         ::eglDestroySurface(egl_display, egl_window_surface);
         Dcheckeglerror(eglDestroySurface);

         egl_window_surface = NULL;

         yac_host->printf("[dbg] omap3::gles_exit: eglDestroySurface() called\n");
      }

      ::eglTerminate(egl_display);

      egl_display = NULL;

      yac_host->printf("[dbg] omap3::gles_exit: eglTerminate() called\n");
   }
}

// ---------------------------------------------------------------------------- loc_gles_init()
static int loc_gles_init(void) {
   int ret;
   EGLBoolean r;
   EGLint eglVerMaj;
   EGLint eglVerMin;

   egl_display = ::eglGetDisplay(EGL_DEFAULT_DISPLAY);

   yac_host->printf("[dbg] omap3::gles_init: egl_display=0x%08x\n", (sU32) egl_display);

   r = ::eglInitialize(egl_display,
                       &eglVerMaj,
                       &eglVerMin
                       );

   if(0 != r)
   {
      EGLint numCfg = 0;

      yac_host->printf("[dbg] omap3::gles_init: eglInitialize() returned eglVerMaj=%d eglVerMin=%d\n",
                       eglVerMaj, eglVerMin
                       );
      Dcheckeglerror(eglInitialize);

      r = ::eglChooseConfig(egl_display, egl_config_attribs, &egl_config, 1, &numCfg);
      Dcheckeglerror(eglChooseConfig);

      if(0 != r)
      {
         if(numCfg >= 1)
         {
            yac_host->printf("[dbg] omap3::gles_init: eglChooseConfig returned numCfg=%u\n", numCfg);

            egl_window_surface = ::eglCreateWindowSurface(egl_display,
                                                          egl_config,
                                                          NULL,  /* window */
                                                          egl_window_surface_attribs  /* window_attribs */
                                                          );
            Dcheckeglerror(eglCreateWindowSurface);

            if(EGL_NO_SURFACE != egl_window_surface)
            {
               yac_host->printf("[...] omap3::gles_init: ok, got egl_window_surface=0x%08x\n", (sU32)egl_window_surface);

#ifdef USE_PBUFFER
               egl_pbuffer_surface = ::eglCreatePbufferSurface(egl_display,
                                                               egl_config,
                                                               NULL  /* pbuffer_attribs */
                                                               );
               Dcheckeglerror(eglCreatePbufferSurface);
               
               if(EGL_NO_SURFACE != egl_pbuffer_surface)
               {
                  yac_host->printf("[...] omap3::gles_init: ok, got egl_pbuffer_surface=0x%08x\n", (sU32)egl_pbuffer_surface);
#endif /* USE_PBUFFER */
                  
                  egl_context = ::eglCreateContext(egl_display,
                                                   egl_config,
                                                   EGL_NO_CONTEXT,
                                                   egl_context_attribs
                                                   );
                  Dcheckeglerror(eglCreateContext);
                  
                  if(EGL_NO_CONTEXT != egl_context)
                  {
                     yac_host->printf("[...] omap3::gles_init: ok, got egl_context=0x%08x\n", (sU32)egl_context);
                     
                     ::eglMakeCurrent(egl_display, egl_window_surface, egl_window_surface, egl_context);

#ifdef USE_PBUFFER
                     //eglMakeCurrent(egl_display, egl_pbuffer_surface, egl_pbuffer_surface, egl_context);
#endif
                     Dcheckeglerror(eglMakeCurrent);
                     
#if 0
                     /* (note) does not work / no effect */
                     ::eglSwapInterval(egl_display, 1);
                     Dcheckeglerror(eglSwapInterval);
#endif
                     
                     /* Do some queries */
                     {
                        const GLubyte *str;
                        
                        str = ::glGetString(GL_VENDOR);
                        Dcheckglerror(glGetString);
                       
                        yac_host->printf("[...] omap3::gles_init: GL_VENDOR = \"%s\".\n", str);
                        

                        str = ::glGetString(GL_RENDERER);
                        Dcheckglerror(glGetString);
                       
                        yac_host->printf("[...] omap3::gles_init: GL_RENDERER = \"%s\".\n", str);


                        str = ::glGetString(GL_VERSION);
                        Dcheckglerror(glGetString);
                       
                        yac_host->printf("[...] omap3::gles_init: GL_VERSION = \"%s\".\n", str);

                        // pvr-specific version info:
                        {
                           char buf[1024];

                           FILE *fh;

                           fh = fopen("/proc/pvr/version", "rb");

                           if(NULL != fh)
                           {
                              size_t numRead = fread(buf, 1, 1023, fh);

                              buf[numRead]=0;

                              yac_host->printf("[...] omap3::gles_init: PowerVR version:\n");
                              yac_host->printf(buf); // (todo) dangerous

                              fclose(fh);
                           }
                        }


                        str = ::glGetString(GL_EXTENSIONS);
                        Dcheckglerror(glGetString);
                       
                        yac_host->printf("[...] omap3::gles_init: GL_EXTENSIONS = \"%s\".\n", str);

                        
                        loc_gles_query_print_int(GL_ALPHA_BITS,
                                                 "gles_init:        GL_ALPHA_BITS = "
                                                 );
                        
                        
                        loc_gles_query_print_int(GL_RED_BITS, 
                                                 "gles_init:          GL_RED_BITS = "
                                                 );
                        
                        loc_gles_query_print_int(GL_GREEN_BITS, 
                                                 "gles_init:        GL_GREEN_BITS = "
                                                 );
                        
                        loc_gles_query_print_int(GL_BLUE_BITS, 
                                                 "gles_init:         GL_BLUE_BITS = "
                                                 );
                        
                        loc_gles_query_print_int(GL_STENCIL_BITS, 
                                                 "gles_init:      GL_STENCIL_BITS = "
                                                 );
                        
                        loc_gles_query_print_int(GL_DEPTH_BITS,
                                                 "gles_init:        GL_DEPTH_BITS = "
                                                 );
                        
                        loc_gles_query_print_int(GL_SAMPLE_BUFFERS, 
                                                 "gles_init:    GL_SAMPLE_BUFFERS = "
                                                 );
                        
                        loc_gles_query_print_int(GL_SAMPLES, 
                                                 "gles_init:           GL_SAMPLES = "
                                                 );
                        
                        loc_gles_query_print_int2(GL_MAX_VIEWPORT_DIMS, 
                                                  "gles_init: GL_MAX_VIEWPORT_DIMS = "
                                                  );
                        
#if 0                       
                        loc_gles_query_print_int(GL_MAX_TEXTURE_UNITS, 
                                                 "gles_init: GL_MAX_TEXTURE_UNITS = "
                                                 );
#endif

                        loc_gles_query_print_int(GL_SHADER_COMPILER, 
                                                 "gles_init:   GL_SHADER_COMPILER = "
                                                 );
                     }
                  
                     /* Succeeded */
                     
                     {
                        EGLenum eglErr;
                        GLenum glErr;
                        
                        eglErr = ::eglGetError();
                        glErr  =  ::glGetError();
                        
                        yac_host->printf("[dbg] omap3::gles_init: ok, eglGetError()=%d (0x%08x), glGetError=%d (0x%08x)\n",
                                         eglErr, eglErr,
                                         glErr, glErr
                                         );
                     }

                     //glViewport(0, 0, FB_W, FB_H);
                     //glViewport(0, 20, FB_W, FB_H-20);
                     
                     ret = 0;
                  }
                  else
                  {
                     yac_host->printf("[---] omap3::gles_init: eglCreateContext() returned EGL_NO_CONTEXT.\n");
                     
#ifdef USE_PBUFFER
                     ::eglDestroySurface(egl_display, egl_pbuffer_surface);
                     Dcheckeglerror(eglDestroySurface);
                     egl_pbuffer_surface = NULL;
#endif
                     
                     ::eglDestroySurface(egl_display, egl_window_surface);
                     Dcheckeglerror(eglDestroySurface);
                     egl_window_surface = NULL;
                     
                     ::eglTerminate(egl_display);
                     egl_display = NULL;
                     
                     ret = 50;
                  }
#ifdef USE_PBUFFER
               }
               else
               {
                  yac_host->printf("[---] omap3::gles_init: eglCreatePbufferSurface() returned EGL_NO_SURFACE.\n");

                  ::eglDestroySurface(egl_display, egl_window_surface);
                  Dcheckeglerror(eglDestroySurface);
                  egl_window_surface = NULL;
                  
                  ::eglTerminate(egl_display);
                  egl_display = NULL;

                  ret = 40;
               }
#endif /* USE_PBUFFER */
            }
            else
            {
               yac_host->printf("[---] omap3::gles_init: "
                                "eglCreateWindowSurface()"
                                " returned EGL_NO_SURFACE.\n"
                                );
               
               ::eglTerminate(egl_display);
               egl_display = NULL;

               ret = 30;
            }
         }
         else
         {
            yac_host->printf("[---] omap3::gles_init: eglChooseConfig() returned numCfg=%d.\n", numCfg);

            ::eglTerminate(egl_display);
            egl_display = NULL;

            ret = 20;
         }
      }
      else
      {
         yac_host->printf("[---] omap3::gles_init: eglChooseConfig() failed.\n");

         ::eglTerminate(egl_display);
         egl_display = NULL;

         ret = 10;
      }

   }
   else
   {
      yac_host->printf("[---] omap3::gles_init: eglInitialize() failed.\n");

      ret = 10;
   }

   return ret;
}

// ---------------------------------------------------------------------------- omap3_view_open()
sBool omap3_view_open(void) {
   sBool ret = YAC_FALSE;
   int r;

   yac_host->printf("[dbg] omap3_view_open() called\n");

   r = loc_gles_init();

   if(0 == r)
   {
      fb_fd = ::open("/dev/fb0", O_RDWR);

      if(fb_fd > 0)
      {
         ////printf("xxx open fb_fd=%d\n", fb_fd);
      }
      else
      {
         yac_host->printf("[---] omap3_view_open: failed to open /dev/fb0\n");
      }

      ret = YAC_TRUE;
   }
   else
   {
      yac_host->printf("[---] omap3_view_open: gles_init() failed. r=%d\n", r);
   }

   return ret;
}

// ---------------------------------------------------------------------------- omap3_view_swapbuffers()
void omap3_view_swapbuffers(sUI _swapInterval) {
   ::eglSwapBuffers(egl_display, egl_window_surface);

   sUI i;

   for(i=0; i<_swapInterval; i++)
   {
      ::ioctl(fb_fd, OMAPFB_WAITFORVSYNC);
   }

   ////::eglSwapInterval(egl_display, 1); // no effect
}

// ---------------------------------------------------------------------------- omap3_view_close()
void omap3_view_close(void) {

   yac_host->printf("[dbg] omap3_view_close() called\n");

   if(-1 != fb_fd)
   {
      ::close(fb_fd);
      fb_fd = -1;
   }

   loc_gles_exit();
}

