/*
 * 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:
 *  Matthew Allum
 *  Robert Bragg
 *  Kristian Høgsberg
 */

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

#include <fcntl.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include <wayland-util.h>
#include <wayland-client.h>
#include <xf86drm.h>

#include "clutter-stage-wayland.h"
#include "clutter-wayland.h"
#include "clutter-backend-wayland.h"

#include "clutter-actor-private.h"
#include "clutter-debug.h"
#include "clutter-event.h"
#include "clutter-enum-types.h"
#include "clutter-feature.h"
#include "clutter-main.h"
#include "clutter-private.h"
#include "clutter-stage-private.h"

#include "cogl/cogl-framebuffer-private.h"

static void
wayland_swap_buffers (ClutterStageWayland *stage_wayland);

static void clutter_stage_window_iface_init (ClutterStageWindowIface *iface);

G_DEFINE_TYPE_WITH_CODE (ClutterStageWayland,
                         _clutter_stage_wayland,
                         G_TYPE_OBJECT,
                         G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_STAGE_WINDOW,
                                                clutter_stage_window_iface_init));

#if G_BYTE_ORDER == G_BIG_ENDIAN
#define VISUAL_ARGB_PRE COGL_PIXEL_FORMAT_ARGB_8888_PRE
#define VISUAL_ARGB     COGL_PIXEL_FORMAT_ARGB_8888
#define VISUAL_RGB      COGL_PIXEL_FORMAT_RGB_888
#elif G_BYTE_ORDER == G_LITTLE_ENDIAN
#define VISUAL_ARGB_PRE COGL_PIXEL_FORMAT_BGRA_8888_PRE
#define VISUAL_ARGB     COGL_PIXEL_FORMAT_BGRA_8888
#define VISUAL_RGB      COGL_PIXEL_FORMAT_BGR_888
#endif

static struct wl_visual *
get_visual (struct wl_display *display, CoglPixelFormat format)
{
  switch (format)
    {
    case VISUAL_ARGB_PRE:
      return wl_display_get_premultiplied_argb_visual (display);
    case VISUAL_ARGB:
      return wl_display_get_argb_visual (display);
    case VISUAL_RGB:
      return wl_display_get_rgb_visual (display);
    default:
      return NULL;
    }
}
static ClutterStageWaylandWaylandBuffer *
wayland_create_shm_buffer (ClutterBackendWayland *backend_wayland,
                           ClutterGeometry *geom)
{
  ClutterStageWaylandWaylandBufferSHM *buffer;
  struct wl_visual *visual;
  CoglHandle tex;
  CoglTextureFlags flags = COGL_TEXTURE_NONE; /* XXX: tweak flags? */
  CoglPixelFormat format = VISUAL_ARGB_PRE;
  int fd;
  gchar tmp[] = "/tmp/clutter-wayland-shm-XXXXXX";

  buffer = g_slice_new (ClutterStageWaylandWaylandBufferSHM);

  buffer->buffer.type = BUFFER_TYPE_SHM;

  tex = cogl_texture_new_with_size ((unsigned int)geom->width,
			       (unsigned int)geom->height,
			       flags, format);
  buffer->format = format;
  buffer->stride = cogl_texture_get_rowstride(tex);
  buffer->size = cogl_texture_get_data(tex, format, buffer->stride, NULL);
  buffer->buffer.tex = tex;

  fd = g_mkstemp_full(tmp, O_RDWR, 0600);
  ftruncate(fd, buffer->size);
  buffer->data = mmap(NULL, buffer->size, PROT_READ | PROT_WRITE,
	              MAP_SHARED, fd, 0);

  g_unlink(tmp);

  visual = get_visual (backend_wayland->wayland_display, format);

  buffer->buffer.wayland_buffer =
    wl_shm_create_buffer (backend_wayland->wayland_shm,
                          fd,
                          geom->width,
                          geom->height,
                          buffer->stride, visual);
  close(fd);
  return &buffer->buffer;
}

static ClutterStageWaylandWaylandBuffer *
wayland_create_drm_buffer (ClutterBackendWayland *backend_wayland,
                           ClutterGeometry *geom)
{
  EGLDisplay edpy = clutter_wayland_get_egl_display ();
  struct wl_visual *visual;
  EGLint name, stride;
  ClutterStageWaylandWaylandBufferDRM *buffer;
  EGLint image_attribs[] = {
      EGL_WIDTH, 0,
      EGL_HEIGHT, 0,
      EGL_DRM_BUFFER_FORMAT_MESA, EGL_DRM_BUFFER_FORMAT_ARGB32_MESA,
      EGL_DRM_BUFFER_USE_MESA, EGL_DRM_BUFFER_USE_SCANOUT_MESA,
      EGL_NONE
  };

  buffer = g_slice_new (ClutterStageWaylandWaylandBufferDRM);

  buffer->buffer.type = BUFFER_TYPE_DRM;

  image_attribs[1] = geom->width;
  image_attribs[3] = geom->height;
  buffer->drm_image = backend_wayland->create_drm_image (edpy, image_attribs);
  glGenTextures (1, &buffer->texture);
  glBindTexture (GL_TEXTURE_2D, buffer->texture);
  backend_wayland->image_target_texture_2d (GL_TEXTURE_2D, buffer->drm_image);

  buffer->buffer.tex = cogl_texture_new_from_foreign (buffer->texture,
					       GL_TEXTURE_2D,
					       geom->width,
					       geom->height,
					       0,
					       0,
					       VISUAL_ARGB_PRE);

  backend_wayland->export_drm_image (edpy, buffer->drm_image,
				     &name, NULL, &stride);
  visual = get_visual (backend_wayland->wayland_display, VISUAL_ARGB_PRE);
  buffer->buffer.wayland_buffer =
    wl_drm_create_buffer (backend_wayland->wayland_drm,
                          name,
                          geom->width,
                          geom->height,
                          stride, visual);

  return &buffer->buffer;
}

static ClutterStageWaylandWaylandBuffer *
wayland_create_buffer (ClutterGeometry *geom)
{
  ClutterBackend *backend = clutter_get_default_backend ();
  ClutterBackendWayland *backend_wayland = CLUTTER_BACKEND_WAYLAND (backend);
  ClutterStageWaylandWaylandBuffer *buffer;
  cairo_rectangle_int_t rect;

  if (backend_wayland->drm_enabled &&
      backend_wayland->wayland_drm != NULL)
    buffer = wayland_create_drm_buffer (backend_wayland, geom);
  else if (backend_wayland->wayland_shm != NULL)
    buffer = wayland_create_shm_buffer (backend_wayland, geom);
  else
    return NULL;

  buffer->offscreen = cogl_offscreen_new_to_texture (buffer->tex);

  rect.x = geom->x;
  rect.y = geom->y;
  rect.width = geom->width;
  rect.height = geom->height;
  buffer->dirty_region = cairo_region_create_rectangle (&rect);

  return buffer;
}

static void
wayland_free_shm_buffer (ClutterStageWaylandWaylandBuffer *generic_buffer)
{
  ClutterStageWaylandWaylandBufferSHM *buffer;

  buffer = (ClutterStageWaylandWaylandBufferSHM *)generic_buffer;

  munmap(buffer->data, buffer->size);
  g_slice_free (ClutterStageWaylandWaylandBufferSHM, buffer);
}

static void
wayland_free_drm_buffer (ClutterStageWaylandWaylandBuffer *generic_buffer)
{
  ClutterBackend *backend = clutter_get_default_backend ();
  ClutterBackendWayland *backend_wayland = CLUTTER_BACKEND_WAYLAND (backend);
  EGLDisplay edpy = clutter_wayland_get_egl_display ();
  ClutterStageWaylandWaylandBufferDRM *buffer;

  buffer = (ClutterStageWaylandWaylandBufferDRM *)generic_buffer;

  glDeleteTextures (1, &buffer->texture);
  backend_wayland->destroy_image (edpy, buffer->drm_image);
  g_slice_free (ClutterStageWaylandWaylandBufferDRM, buffer);
}

static void
wayland_free_buffer (ClutterStageWaylandWaylandBuffer *buffer)
{
  cogl_handle_unref (buffer->tex);
  wl_buffer_destroy (buffer->wayland_buffer);
  cogl_handle_unref (buffer->offscreen);

  if (buffer->type == BUFFER_TYPE_DRM)
    wayland_free_drm_buffer(buffer);
  else if (buffer->type == BUFFER_TYPE_SHM)
    wayland_free_shm_buffer(buffer);
}

static void
clutter_stage_wayland_unrealize (ClutterStageWindow *stage_window)
{
  ClutterStageWayland *stage_wayland = CLUTTER_STAGE_WAYLAND (stage_window);

  if (stage_wayland->front_buffer)
    {
      wayland_free_buffer (stage_wayland->front_buffer);
      stage_wayland->front_buffer = NULL;
    }

  if (stage_wayland->back_buffer)
    {
      wayland_free_buffer (stage_wayland->back_buffer);
      stage_wayland->back_buffer = NULL;
    }

  wayland_free_buffer (stage_wayland->pick_buffer);
}

static gboolean
clutter_stage_wayland_realize (ClutterStageWindow *stage_window)
{
  ClutterStageWayland *stage_wayland = CLUTTER_STAGE_WAYLAND (stage_window);
  ClutterBackend *backend = clutter_get_default_backend ();
  ClutterBackendWayland *backend_wayland = CLUTTER_BACKEND_WAYLAND (backend);
  gfloat width, height;

  clutter_actor_get_size (CLUTTER_ACTOR (stage_wayland->wrapper),
			  &width, &height);
  stage_wayland->pending_allocation.width = (gint)width;
  stage_wayland->pending_allocation.height = (gint)height;
  stage_wayland->allocation = stage_wayland->pending_allocation;

  stage_wayland->wayland_surface =
    wl_compositor_create_surface (backend_wayland->wayland_compositor);
  wl_surface_set_user_data (stage_wayland->wayland_surface, stage_wayland);

  stage_wayland->pick_buffer =
    wayland_create_buffer (&stage_wayland->allocation);

  return TRUE;
}

static int
clutter_stage_wayland_get_pending_swaps (ClutterStageWindow *stage_window)
{
  ClutterStageWayland *stage_wayland = CLUTTER_STAGE_WAYLAND (stage_window);

  return stage_wayland->pending_swaps;
}

static void
clutter_stage_wayland_set_fullscreen (ClutterStageWindow *stage_window,
				      gboolean            fullscreen)
{
  g_warning ("Stage of type '%s' do not support ClutterStage::set_fullscreen",
             G_OBJECT_TYPE_NAME (stage_window));
}

static void
clutter_stage_wayland_set_title (ClutterStageWindow *stage_window,
				 const gchar        *title)
{
  g_warning ("Stage of type '%s' do not support ClutterStage::set_title",
             G_OBJECT_TYPE_NAME (stage_window));
}

static void
clutter_stage_wayland_set_cursor_visible (ClutterStageWindow *stage_window,
					  gboolean            cursor_visible)
{
  g_warning ("Stage of type '%s' do not support ClutterStage::set_cursor_visible",
             G_OBJECT_TYPE_NAME (stage_window));
}

static ClutterActor *
clutter_stage_wayland_get_wrapper (ClutterStageWindow *stage_window)
{
  return CLUTTER_ACTOR (CLUTTER_STAGE_WAYLAND (stage_window)->wrapper);
}

static void
clutter_stage_wayland_show (ClutterStageWindow *stage_window,
			    gboolean            do_raise)
{
  ClutterStageWayland *stage_wayland = CLUTTER_STAGE_WAYLAND (stage_window);

  clutter_actor_map (CLUTTER_ACTOR (stage_wayland->wrapper));
}

static void
clutter_stage_wayland_hide (ClutterStageWindow *stage_window)
{
  ClutterStageWayland *stage_wayland = CLUTTER_STAGE_WAYLAND (stage_window);

  clutter_actor_unmap (CLUTTER_ACTOR (stage_wayland->wrapper));
}

static void
clutter_stage_wayland_get_geometry (ClutterStageWindow *stage_window,
				    ClutterGeometry    *geometry)
{
  ClutterStageWayland *stage_wayland = CLUTTER_STAGE_WAYLAND (stage_window);

  if (geometry)
    {
      *geometry = stage_wayland->allocation;
    }
}

static void
clutter_stage_wayland_resize (ClutterStageWindow *stage_window,
			      gint                width,
			      gint                height)
{
  ClutterStageWayland *stage_wayland = CLUTTER_STAGE_WAYLAND (stage_window);
  cairo_rectangle_int_t rect;

  fprintf (stderr, "resize %dx%d\n", width, height);

  stage_wayland->pending_allocation.width = width;
  stage_wayland->pending_allocation.height = height;

  /* FIXME: Shouldn't the stage repaint everything when it gets resized? */
  rect.x = stage_wayland->pending_allocation.x;
  rect.y = stage_wayland->pending_allocation.y;
  rect.width = stage_wayland->pending_allocation.width;
  rect.height = stage_wayland->pending_allocation.height;
  cairo_region_union_rectangle (stage_wayland->repaint_region, &rect);
}

#define CAIRO_REGION_FULL ((cairo_region_t *) 1)

static gboolean
clutter_stage_wayland_has_redraw_clips (ClutterStageWindow *stage_window)
{
  return TRUE;
}

static gboolean
clutter_stage_wayland_ignoring_redraw_clips (ClutterStageWindow *stage_window)
{
  return FALSE;
}

static void
clutter_stage_wayland_add_redraw_clip (ClutterStageWindow *stage_window,
				       ClutterGeometry    *stage_clip)
{
  ClutterStageWayland *stage_wayland = CLUTTER_STAGE_WAYLAND (stage_window);
  cairo_rectangle_int_t rect;

  if (stage_clip == NULL)
    {
      rect.x = stage_wayland->allocation.x;
      rect.y = stage_wayland->allocation.y;
      rect.width = stage_wayland->allocation.width;
      rect.height = stage_wayland->allocation.height;
    }
  else
    {
      rect.x = stage_clip->x;
      rect.y = stage_clip->y;
      rect.width = stage_clip->width;
      rect.height = stage_clip->height;
    }

  if (stage_wayland->repaint_region == NULL)
    stage_wayland->repaint_region = cairo_region_create_rectangle (&rect);
  else
    cairo_region_union_rectangle (stage_wayland->repaint_region, &rect);
}

static void
clutter_stage_window_iface_init (ClutterStageWindowIface *iface)
{
  iface->realize = clutter_stage_wayland_realize;
  iface->unrealize = clutter_stage_wayland_unrealize;
  iface->get_pending_swaps = clutter_stage_wayland_get_pending_swaps;
  iface->set_fullscreen = clutter_stage_wayland_set_fullscreen;
  iface->set_title = clutter_stage_wayland_set_title;
  iface->set_cursor_visible = clutter_stage_wayland_set_cursor_visible;
  iface->get_wrapper = clutter_stage_wayland_get_wrapper;
  iface->get_geometry = clutter_stage_wayland_get_geometry;
  iface->resize = clutter_stage_wayland_resize;
  iface->show = clutter_stage_wayland_show;
  iface->hide = clutter_stage_wayland_hide;

  iface->add_redraw_clip = clutter_stage_wayland_add_redraw_clip;
  iface->has_redraw_clips = clutter_stage_wayland_has_redraw_clips;
  iface->ignoring_redraw_clips = clutter_stage_wayland_ignoring_redraw_clips;
}

static void
_clutter_stage_wayland_class_init (ClutterStageWaylandClass *klass)
{
}

static void
_clutter_stage_wayland_init (ClutterStageWayland *stage_wayland)
{
  stage_wayland->allocation.x = 0;
  stage_wayland->allocation.y = 0;
  stage_wayland->allocation.width = 640;
  stage_wayland->allocation.height = 480;
  stage_wayland->save_allocation = stage_wayland->allocation;
}

static void
wayland_frame_callback (void *data, uint32_t _time)
{
  ClutterStageWayland *stage_wayland = data;

  stage_wayland->pending_swaps--;
}

static void
wayland_damage_buffer(ClutterStageWaylandWaylandBuffer *generic_buffer)
{
  ClutterStageWaylandWaylandBufferSHM *buffer;
  int size;

  if (generic_buffer->type != BUFFER_TYPE_SHM)
    return;

  buffer = (ClutterStageWaylandWaylandBufferSHM *)generic_buffer;

  size = cogl_texture_get_data(buffer->buffer.tex, buffer->format,
                               buffer->stride, NULL);
  g_assert(size == (int)buffer->size);

  (void) cogl_texture_get_data(buffer->buffer.tex, buffer->format,
                               buffer->stride, buffer->data);

}

static void
wayland_swap_buffers (ClutterStageWayland *stage_wayland)
{
  ClutterBackend *backend = clutter_get_default_backend ();
  ClutterBackendWayland *backend_wayland = CLUTTER_BACKEND_WAYLAND (backend);
  ClutterStageWaylandWaylandBuffer *buffer;

  buffer = stage_wayland->front_buffer;
  stage_wayland->front_buffer = stage_wayland->back_buffer;
  stage_wayland->back_buffer = buffer;

  wayland_damage_buffer(stage_wayland->front_buffer);

  wl_surface_attach (stage_wayland->wayland_surface,
                     stage_wayland->front_buffer->wayland_buffer,
  /* 0,0 here is "relative to the old buffer," not absolute */
                     0, 0);
  wl_surface_map_toplevel (stage_wayland->wayland_surface);

  stage_wayland->pending_swaps++;
  wl_display_frame_callback (backend_wayland->wayland_display,
			     wayland_frame_callback,
			     stage_wayland);

  _cogl_swap_buffers_notify ();
}

static void
_clutter_stage_wayland_repair_dirty(ClutterStageWayland *stage_wayland,
				       ClutterStage     *stage)
{
  CoglMaterial *outline = NULL;
  CoglHandle vbo;
  float vertices[8], texcoords[8];
  CoglMatrix modelview;
  cairo_region_t *dirty;
  cairo_rectangle_int_t rect;
  int i, count;
  float width, height;

  dirty = stage_wayland->back_buffer->dirty_region;
  stage_wayland->back_buffer->dirty_region = NULL;
  cairo_region_subtract (dirty, stage_wayland->repaint_region);
  width = stage_wayland->allocation.width;
  height = stage_wayland->allocation.height;
  
  /* If this is the first time we render, there is no front buffer to
   * copy back from, but then the dirty region not covered by the
   * repaint should be empty, because we repaint the entire stage.
   *
   * assert(stage_wayland->front_buffer != NULL) ||
   *   cairo_region_is_empty(dirty);
   *
   * FIXME: in test-rotate, the stage never queues a full repaint
   * initially, it's restricted to the paint box of it's rotating
   * children.
   */

  if (!stage_wayland->front_buffer)
    return;

  outline = cogl_material_new ();
  cogl_material_set_layer (outline, 0, stage_wayland->front_buffer->tex);
  count = cairo_region_num_rectangles (dirty);

  for (i = 0; i < count; i++)
    {
      cairo_region_get_rectangle (dirty, i, &rect);
      vbo = cogl_vertex_buffer_new (4);

      vertices[0] = rect.x - 1;
      vertices[1] = rect.y - 1;
      vertices[2] = rect.x + rect.width + 1;
      vertices[3] = rect.y - 1;
      vertices[4] = rect.x + rect.width + 1;
      vertices[5] = rect.y + rect.height + 1;
      vertices[6] = rect.x - 1;
      vertices[7] = rect.y + rect.height + 1;

      cogl_vertex_buffer_add (vbo,
			      "gl_Vertex",
			      2, /* n_components */
			      COGL_ATTRIBUTE_TYPE_FLOAT,
			      FALSE, /* normalized */
			      0, /* stride */
			      vertices);

      texcoords[0] = vertices[0] / width;
      texcoords[1] = vertices[1] / height;
      texcoords[2] = vertices[2] / width;
      texcoords[3] = vertices[3] / height;
      texcoords[4] = vertices[4] / width;
      texcoords[5] = vertices[5] / height;
      texcoords[6] = vertices[6] / width;
      texcoords[7] = vertices[7] / height;

      cogl_vertex_buffer_add (vbo,
			      "gl_MultiTexCoord0",
			      2, /* n_components */
			      COGL_ATTRIBUTE_TYPE_FLOAT,
			      FALSE, /* normalized */
			      0, /* stride */
			      texcoords);

      cogl_vertex_buffer_submit (vbo);

      cogl_push_matrix ();
      cogl_matrix_init_identity (&modelview);
      _clutter_actor_apply_modelview_transform (CLUTTER_ACTOR (stage),
						&modelview);
      cogl_set_modelview_matrix (&modelview);
      cogl_set_source (outline);
      cogl_vertex_buffer_draw (vbo, COGL_VERTICES_MODE_TRIANGLE_FAN,
			       0 , 4);
      cogl_pop_matrix ();
      cogl_object_unref (vbo);
    }

  cairo_region_destroy (dirty);
}

void
_clutter_stage_wayland_repaint_region (ClutterStageWayland *stage_wayland,
				       ClutterStage        *stage)
{
  ClutterGeometry geom;
  cairo_rectangle_int_t rect;
  int i, count;

  count = cairo_region_num_rectangles (stage_wayland->repaint_region);
  for (i = 0; i < count; i++)
    {
      cairo_region_get_rectangle (stage_wayland->repaint_region, i, &rect);

      cogl_clip_push_window_rectangle (rect.x - 1, rect.y - 1,
				       rect.width + 2, rect.height + 2);

      geom.x = rect.x;
      geom.y = rect.y;
      geom.width = rect.width;
      geom.height = rect.height;
      /* FIXME: We should pass geom in as second arg, but some actors
       * cull themselves a little to much.  Disable for now.*/
      _clutter_stage_do_paint (stage, NULL);

      cogl_clip_pop ();
    }
}

void
_clutter_stage_wayland_redraw (ClutterStageWayland *stage_wayland,
			       ClutterStage    *stage)
{
  stage_wayland->allocation = stage_wayland->pending_allocation;

  if (!stage_wayland->back_buffer)
      stage_wayland->back_buffer =
	wayland_create_buffer (&stage_wayland->allocation);

  cogl_set_framebuffer (stage_wayland->back_buffer->offscreen);
  _clutter_stage_maybe_setup_viewport (stage_wayland->wrapper);

  _clutter_stage_wayland_repair_dirty (stage_wayland, stage);

  _clutter_stage_wayland_repaint_region (stage_wayland, stage);

  cogl_flush ();
  glFlush ();

  wayland_swap_buffers (stage_wayland);

  if (stage_wayland->back_buffer)
    stage_wayland->back_buffer->dirty_region = stage_wayland->repaint_region;
  else
    cairo_region_destroy (stage_wayland->repaint_region);

  stage_wayland->repaint_region = NULL;

  cogl_set_framebuffer (stage_wayland->pick_buffer->offscreen);
  _clutter_stage_maybe_setup_viewport (stage_wayland->wrapper);
}
