/*  RetroArch - A frontend for libretro.
 *  Copyright (C) 2016-2017 - Alfred Agrell
 *  Copyright (C) 2011-2017 - Daniel De Matteis
 *
 *  RetroArch is free software: you can redistribute it and/or modify it under the terms
 *  of the GNU General Public License as published by the Free Software Found-
 *  ation, either version 3 of the License, or (at your option) any later version.
 *
 *  RetroArch 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with RetroArch.
 *  If not, see <http://www.gnu.org/licenses/>.
 */

#define _XOPEN_SOURCE 700 /* TODO: this doesn't really belong here. */

#include <stdlib.h>
#include <string.h>

#include <sys/shm.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/XShm.h>

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

#ifdef HAVE_MENU
#include "../../menu/menu_driver.h"
#endif

#include "../common/x11_common.h"
#include "../../configuration.h"
#include "../../verbosity.h"

typedef struct xshm
{
   XShmSegmentInfo shmInfo;
   XImage* image;
   uint8_t *fbptr;
   GC gc;
   int width;
   int height;
   bool use_shm;
} xshm_t;

static void *xshm_init(const video_info_t *video,
      input_driver_t **input, void **input_data)
{
   xshm_t* xshm = (xshm_t*)malloc(sizeof(xshm_t));
   Window parent;
   XSetWindowAttributes attributes;

   XInitThreads();

   g_x11_dpy     = XOpenDisplay(NULL);

   xshm->use_shm = true;

   if (!XShmQueryExtension(g_x11_dpy))
   {
      RARCH_LOG("[X11]: XShm extension not found.\n");
      xshm->use_shm        = false;
   }

#ifdef RARCH_INTERNAL
   parent                  = DefaultRootWindow(g_x11_dpy);
#else
   parent                  = video->parent;
#endif
   attributes.border_pixel = 0;
   g_x11_win               = XCreateWindow(g_x11_dpy, parent,
			     0, 0, video->width, video->height,
			     0, 24, CopyFromParent, NULL, CWBorderPixel, &attributes);
   XSetWindowBackground(g_x11_dpy, g_x11_win, 0);
   XMapWindow(g_x11_dpy, g_x11_win);

   if (xshm->use_shm)
   {
      xshm->shmInfo.shmid = shmget(IPC_PRIVATE, sizeof(uint32_t) * video->width * video->height,
				   IPC_CREAT | 0600);
      if (xshm->shmInfo.shmid < 0)
         abort();/* seems like an OOM situation... let's just blow up. */

      xshm->shmInfo.shmaddr  = (char*)shmat(xshm->shmInfo.shmid, 0, 0);
      xshm->shmInfo.readOnly = False;
      XShmAttach(g_x11_dpy, &xshm->shmInfo);
      XSync(g_x11_dpy, False);/* no idea why this is required, but I get weird errors without it. */
      xshm->image = XShmCreateImage(g_x11_dpy, NULL, 24, ZPixmap,
				    xshm->shmInfo.shmaddr, &xshm->shmInfo, video->width, video->height);
      xshm->fbptr = (uint8_t*)xshm->shmInfo.shmaddr;
   }
   else
   {
      size_t pitch = video->width * 4;
      void *data   = malloc (pitch * video->height);
      if (!data)
         abort(); /* seems like an OOM situation... let's just blow up. */
      xshm->image  = XCreateImage(g_x11_dpy, NULL, 24, ZPixmap, 0,
				 (char *) data, video->width,
				 video->height, 8, pitch);
      xshm->fbptr  = (uint8_t*)data;
      XSync(g_x11_dpy, False);
   }

   xshm->gc     = XCreateGC(g_x11_dpy, g_x11_win, 0, NULL);

   xshm->width  = video->width;
   xshm->height = video->height;

   if (!x11_input_ctx_new(true))
      goto error;

   if (input && input_data)
   {
      settings_t *settings                   = config_get_ptr();
      void *xinput                           = input_driver_init_wrap(&input_x,
            settings->arrays.input_joypad_driver);
      if (xinput)
      {
         *input = &input_x;
         *input_data = xinput;
      }
      else
         *input = NULL;
   }

   return xshm;
 error:
   free (xshm);
   return NULL;
}

static bool xshm_frame(void *data, const void *frame, unsigned width,
      unsigned height, uint64_t frame_count,
      unsigned pitch, const char *msg, video_frame_info_t *video_info)
{
   unsigned y;
   xshm_t      *xshm  = (xshm_t*)data;
#ifdef HAVE_MENU
   bool menu_is_alive = (video_info->menu_st_flags & MENU_ST_FLAG_ALIVE) ? true : false;
#endif

   for (y = 0; y < height; y++)
      memcpy(xshm->fbptr + sizeof(uint32_t)*xshm->width*y,
            (uint8_t*)frame + pitch*y, pitch);

#ifdef HAVE_MENU
   menu_driver_frame(menu_is_alive, video_info);
#endif

   if (xshm->use_shm)
      XShmPutImage(g_x11_dpy, g_x11_win, xshm->gc, xshm->image,
		   0, 0, 0, 0, xshm->width, xshm->height, False);
   else
      XPutImage(g_x11_dpy, g_x11_win, xshm->gc, xshm->image,
		0, 0, 0, 0, xshm->width, xshm->height);
   XFlush(g_x11_dpy);

   return true;
}

static void xshm_set_nonblock_state(void *a, bool b, bool c, unsigned d) { }
static bool xshm_alive(void *data) { return true; }
static bool xshm_focus(void *data) { return true; }
static bool xshm_suppress_screensaver(void *data, bool enable) { return false; }
static void xshm_free(void *data) { }
static void xshm_poke_set_filtering(void *data, unsigned index, bool smooth, bool ctx_scaling) { }
static void xshm_poke_set_aspect_ratio(void *data, unsigned aspect_ratio_idx) { }
static void xshm_poke_apply_state_changes(void *data) { }
static void xshm_poke_set_texture_frame(void *data,
      const void *frame, bool rgb32,
      unsigned width, unsigned height, float alpha) { }
static void xshm_poke_texture_enable(void *data,
      bool enable, bool full_screen) { }
static void xshm_poke_set_osd_msg(void *data,
      const char *msg,
      const struct font_params *params, void *font) { }
static void xshm_show_mouse(void *data, bool state) { }
static void xshm_grab_mouse_toggle(void *data) { }

static video_poke_interface_t xshm_video_poke_interface = {
   NULL, /* get_flags */
   NULL, /* load_texture */
   NULL, /* unload_texture */
   NULL, /* set_video_mode */
#ifdef HAVE_XF86VM
   x11_get_refresh_rate,
#else
   NULL, /* get_refresh_rate */
#endif
   xshm_poke_set_filtering,
   NULL, /* get_video_output_size */
   NULL, /* get_video_output_prev */
   NULL, /* get_video_output_next */
   NULL, /* get_current_framebuffer */
   NULL, /* get_proc_address */
   xshm_poke_set_aspect_ratio,
   xshm_poke_apply_state_changes,
   xshm_poke_set_texture_frame,
   xshm_poke_texture_enable,
   xshm_poke_set_osd_msg,
   xshm_show_mouse,
   xshm_grab_mouse_toggle,
   NULL, /* get_current_shader */
   NULL, /* get_current_software_framebuffer */
   NULL, /* get_hw_render_interface */
   NULL, /* set_hdr_max_nits */
   NULL, /* set_hdr_paper_white_nits */
   NULL, /* set_hdr_contrast */
   NULL  /* set_hdr_expand_gamut */
};

static void xshm_get_poke_interface(void *data,
      const video_poke_interface_t **iface)
{
   *iface = &xshm_video_poke_interface;
}
static bool xshm_set_shader(void *data,
      enum rarch_shader_type type, const char *path) { return false; }

video_driver_t video_xshm = {
   xshm_init,
   xshm_frame,
   xshm_set_nonblock_state,
   xshm_alive,
   xshm_focus,
   xshm_suppress_screensaver,
   NULL, /* has_windowed */
   xshm_set_shader,
   xshm_free,
   "x11",
   NULL, /* set_viewport */
   NULL, /* set_rotation */
   NULL, /* viewport_info */
   NULL, /* read_viewport */
   NULL, /* read_frame_raw */
#ifdef HAVE_OVERLAY
   NULL, /* get_overlay_interface */
#endif
   xshm_get_poke_interface,
   NULL, /* wrap_type_to_enum */
#ifdef HAVE_GFX_WIDGETS
   NULL  /* gfx_widgets_enabled */
#endif
};