/*
 * Clutter.
 *
 * An OpenGL based 'interactive canvas' library.
 *
 * Copyright (C) 2010  Intel Corporation.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors:
 *   Tao Zhao <tao.zhao@intel.com>
 *   Damien Lespiau <damien.lespiau@intel.com>
 */

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

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include "clutter-debug.h"
#include "clutter-main.h"

#include "clutter-backend-cex100.h"
#include "clutter-cex100.h"

static gdl_plane_id_t gdl_plane = GDL_PLANE_ID_UPP_C;
static guint gdl_n_buffers = CLUTTER_CEX100_TRIPLE_BUFFERING;

G_DEFINE_TYPE (ClutterBackendCex100,
               clutter_backend_cex100,
               CLUTTER_TYPE_BACKEND_EGL)

#ifdef CLUTTER_ENABLE_DEBUG
static const gchar *
gdl_get_plane_name (gdl_plane_id_t plane)
{
  switch (plane)
    {
    case GDL_PLANE_ID_UPP_A:
      return "UPP_A";
    case GDL_PLANE_ID_UPP_B:
      return "UPP_B";
    case GDL_PLANE_ID_UPP_C:
      return "UPP_C";
    case GDL_PLANE_ID_UPP_D:
      return "UPP_D";
    case GDL_PLANE_ID_UPP_E:
      return "UPP_E";
    default:
      g_assert_not_reached ();
    }

  return NULL; /* never reached */
}
#endif

static gboolean
gdl_plane_init (gdl_display_id_t   dpy,
                gdl_plane_id_t     plane,
                gdl_pixel_format_t pixfmt)
{
  gboolean ret = TRUE;

  gdl_color_space_t   colorSpace = GDL_COLOR_SPACE_RGB;
  gdl_rectangle_t     dstRect;
  gdl_display_info_t  display_info;
  gdl_ret_t           rc = GDL_SUCCESS;

  if (GDL_DISPLAY_ID_0 != dpy && GDL_DISPLAY_ID_1 != dpy)
    {
      g_warning ("Invalid display ID, must be GDL_DISPLAY_ID_0 or "
                 "GDL_DISPLAY_ID_1.");
      return FALSE;
    }

  /* Init GDL library */
  rc = gdl_init (NULL);
  if (rc != GDL_SUCCESS)
    {
      g_warning ("GDL initialize failed. %s", gdl_get_error_string (rc));
      return FALSE;
    }

  rc = gdl_get_display_info (dpy, &display_info);
  if (rc != GDL_SUCCESS)
    {
      g_warning ("GDL failed to get display infomation: %s",
                 gdl_get_error_string (rc));
      gdl_close ();
      return FALSE;
    }

  dstRect.origin.x = 0;
  dstRect.origin.y = 0;
  dstRect.width = display_info.tvmode.width;
  dstRect.height = display_info.tvmode.height;

  /* Configure the plane attribute. */
  rc = gdl_plane_reset (plane);
  if (rc == GDL_SUCCESS)
    rc = gdl_plane_config_begin (plane);

  if (rc == GDL_SUCCESS)
    rc = gdl_plane_set_attr (GDL_PLANE_SRC_COLOR_SPACE, &colorSpace);

  if (rc == GDL_SUCCESS)
    rc = gdl_plane_set_attr (GDL_PLANE_PIXEL_FORMAT, &pixfmt);

  if (rc == GDL_SUCCESS)
    rc = gdl_plane_set_attr (GDL_PLANE_DST_RECT, &dstRect);

  if (rc == GDL_SUCCESS)
    rc = gdl_plane_set_uint (GDL_PLANE_NUM_GFX_SURFACES, gdl_n_buffers);

  if (rc == GDL_SUCCESS)
    rc = gdl_plane_config_end (GDL_FALSE);
  else
    gdl_plane_config_end (GDL_TRUE);

  if (rc != GDL_SUCCESS)
    {
      g_warning ("GDL configuration failed: %s.", gdl_get_error_string (rc));
      ret = FALSE;
    }

  gdl_close ();

  return ret;
}

/*
 * ClutterBackendEGL implementation
 */

static gboolean
clutter_backend_cex100_create_context (ClutterBackend  *backend,
				       GError         **error)
{
  ClutterBackendEGL *backend_egl = CLUTTER_BACKEND_EGL (backend);
  EGLConfig configs[2];
  EGLint config_count;
  EGLBoolean status;
  NativeWindowType window;
  EGLint cfg_attribs[] = {
      EGL_BUFFER_SIZE,     EGL_DONT_CARE,
      EGL_RED_SIZE,        8,
      EGL_GREEN_SIZE,      8,
      EGL_BLUE_SIZE,       8,
      EGL_DEPTH_SIZE,      16,
      EGL_ALPHA_SIZE,      8,
      EGL_BIND_TO_TEXTURE_RGBA, EGL_TRUE,
      EGL_BIND_TO_TEXTURE_RGB, EGL_TRUE,
#ifdef HAVE_COGL_GLES2
      EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
#else /* HAVE_COGL_GLES2 */
      EGL_SURFACE_TYPE,    EGL_WINDOW_BIT,
#endif /* HAVE_COGL_GLES2 */
      EGL_NONE
  };

  if (backend_egl->egl_context != EGL_NO_CONTEXT)
    return TRUE;

  CLUTTER_NOTE (BACKEND, "Using the %s plane", gdl_get_plane_name (gdl_plane));

  /* Start by initializing the GDL plane */
  if (!gdl_plane_init (GDL_DISPLAY_ID_0, gdl_plane, GDL_PF_ARGB_32))
    {
      g_set_error (error, CLUTTER_INIT_ERROR,
                   CLUTTER_INIT_ERROR_BACKEND,
                   "Could not initialize the GDL plane");
      return FALSE;
    }

  window = (NativeWindowType) gdl_plane;

  status = eglGetConfigs (backend_egl->edpy,
                          configs,
                          2,
                          &config_count);

  if (status != EGL_TRUE)
    {
      g_set_error (error, CLUTTER_INIT_ERROR,
                   CLUTTER_INIT_ERROR_BACKEND,
                   "No EGL configurations found");
      return FALSE;
    }

  status = eglChooseConfig (backend_egl->edpy,
                            cfg_attribs,
                            configs,
                            G_N_ELEMENTS (configs),
                            &config_count);

  if (status != EGL_TRUE)
    {
      g_set_error (error, CLUTTER_INIT_ERROR,
                   CLUTTER_INIT_ERROR_BACKEND,
                   "Unable to select a valid EGL configuration");
      return FALSE;
    }

  CLUTTER_NOTE (BACKEND, "Got %i configs", config_count);

  if (status != EGL_TRUE)
    {
      g_set_error (error, CLUTTER_INIT_ERROR,
                   CLUTTER_INIT_ERROR_BACKEND,
                   "Unable to Make Current Context for NULL");
      return FALSE;
    }

  if (G_UNLIKELY (backend_egl->egl_surface != EGL_NO_SURFACE))
    {
      eglDestroySurface (backend_egl->edpy, backend_egl->egl_surface);
      backend_egl->egl_surface = EGL_NO_SURFACE;
    }

  if (G_UNLIKELY (backend_egl->egl_context != NULL))
    {
      eglDestroyContext (backend_egl->edpy, backend_egl->egl_context);
      backend_egl->egl_context = NULL;
    }

  backend_egl->egl_surface = eglCreateWindowSurface (backend_egl->edpy,
                                                     configs[0],
                                                     window,
                                                     NULL);

  if (backend_egl->egl_surface == EGL_NO_SURFACE)
    {
      g_set_error (error, CLUTTER_INIT_ERROR,
                   CLUTTER_INIT_ERROR_BACKEND,
                   "Unable to create EGL window surface");

      return FALSE;
    }

#ifdef HAVE_COGL_GLES2
    {
      static const EGLint attribs[3] = {
          EGL_CONTEXT_CLIENT_VERSION, 2,
          EGL_NONE
      };

      backend_egl->egl_context = eglCreateContext (backend_egl->edpy,
                                                   configs[0],
                                                   EGL_NO_CONTEXT,
                                                   attribs);
    }
#else
  /* Seems some GLES implementations 1.x do not like attribs... */
  backend_egl->egl_context = eglCreateContext (backend_egl->edpy,
                                               configs[0],
                                               EGL_NO_CONTEXT,
                                               NULL);
#endif

  if (backend_egl->egl_context == EGL_NO_CONTEXT)
    {
      g_set_error (error, CLUTTER_INIT_ERROR,
                   CLUTTER_INIT_ERROR_BACKEND,
                   "Unable to create a suitable EGL context");
      return FALSE;
    }

  CLUTTER_NOTE (GL, "Created EGL Context");

  CLUTTER_NOTE (BACKEND, "Setting context");

  /*
   * eglnative can have only one stage, so we store the EGL surface in the
   * backend itself, instead of the StageWindow implementation, and we make it
   * current immediately to make sure the Cogl and Clutter can query the EGL
   * context for features.
   */
  status = eglMakeCurrent (backend_egl->edpy,
                           backend_egl->egl_surface,
                           backend_egl->egl_surface,
                           backend_egl->egl_context);

  eglQuerySurface (backend_egl->edpy,
                   backend_egl->egl_surface,
                   EGL_WIDTH,
                   &backend_egl->surface_width);

  eglQuerySurface (backend_egl->edpy,
                   backend_egl->egl_surface,
                   EGL_HEIGHT,
                   &backend_egl->surface_height);

  CLUTTER_NOTE (BACKEND, "EGL surface is %ix%i",
                backend_egl->surface_width,
                backend_egl->surface_height);

  /*
   * For EGL backend, it needs to clear all the back buffers of the window
   * surface before drawing anything, otherwise the image will be blinking
   * heavily.  The default eglWindowSurface has 3 gdl surfaces as the back
   * buffer, that's why glClear should be called 3 times.
   */
  glClearColor (0.0f, 0.0f, 0.0f, 0.0f);
  glClear (GL_COLOR_BUFFER_BIT);
  eglSwapBuffers (backend_egl->edpy, backend_egl->egl_surface);

  glClear (GL_COLOR_BUFFER_BIT);
  eglSwapBuffers (backend_egl->edpy, backend_egl->egl_surface);

  glClear (GL_COLOR_BUFFER_BIT);
  eglSwapBuffers (backend_egl->edpy, backend_egl->egl_surface);

  return TRUE;
}

/*
 * GObject implementation
 */

static void
clutter_backend_cex100_class_init (ClutterBackendCex100Class *klass)
{
  ClutterBackendClass *backend_class = CLUTTER_BACKEND_CLASS (klass);

  backend_class->create_context   = clutter_backend_cex100_create_context;
}

static void
clutter_backend_cex100_init (ClutterBackendCex100 *self)
{
}

/* every backend must implement this function */
GType
_clutter_backend_impl_get_type (void)
{
  return clutter_backend_cex100_get_type ();
}

void
clutter_cex100_set_plane (gdl_plane_id_t plane)
{
  g_return_if_fail (plane >= GDL_PLANE_ID_UPP_A && plane <= GDL_PLANE_ID_UPP_E);

  gdl_plane = plane;
}

void
clutter_cex100_set_buffering_mode (ClutterCex100BufferingMode mode)
{
  g_return_if_fail (mode == CLUTTER_CEX100_DOUBLE_BUFFERING ||
                    mode == CLUTTER_CEX100_TRIPLE_BUFFERING);

  gdl_n_buffers = mode;
}