/*
 * --- GSMP-COPYRIGHT-NOTE-BEGIN ---
 * 
 * This copyright note is auto-generated by ./scripts/Create-CopyPatch.
 * Please add additional copyright information _after_ the line containing
 * the GSMP-COPYRIGHT-NOTE-END tag. Otherwise it might get removed by
 * the ./scripts/Create-CopyPatch script. Do not edit this copyright text!
 * 
 * GSMP: gfx/src/X11Helper.cc
 * General Sound Manipulation Program is Copyright (C) 2000 - 2004
 *   Valentin Ziegler and René Rebe
 * 
 * This program 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 Foundation; version 2. A copy of the GNU General
 * Public License can be found in the file LICENSE.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANT-
 * ABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 * Public License for more details.
 * 
 * --- GSMP-COPYRIGHT-NOTE-END ---
 */

/*
 * The window capturing was taken from the xscreensaver package - and
 * quite modified. The WindowStayOnTop handling was taken from the
 * xosd package. -ReneR
 */

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

#include "X11Helper.hh"

#include <X11/extensions/Xrender.h>

Visual* find_argb_visual (Display* dpy, int scr)
{
  XVisualInfo         *xvi;
  XVisualInfo         templ;
  int                 nvi;
  int                 i;
  XRenderPictFormat   *format;
  Visual              *visual;
    
  templ.screen = scr;
  templ.depth = 32;
  templ.c_class = TrueColor;
  xvi = XGetVisualInfo (dpy,
			VisualScreenMask |
			VisualDepthMask |
			VisualClassMask,
			&templ,
			&nvi);
  if (!xvi)
    return 0;
  visual = 0;
  for (i = 0; i < nvi; i++)
    {
      format = XRenderFindVisualFormat (dpy, xvi[i].visual);
      if (format->type == PictTypeDirect && format->direct.alphaMask)
        {
	  visual = xvi[i].visual;
	  break;
        }
    }
  
  XFree (xvi);
  return visual;
}

int X11Window::Width (Display* dpy, Window window) {
  if (window == 0)
    window = DefaultRootWindow(dpy);
  XWindowAttributes xgwa;
  XGetWindowAttributes (dpy, window, &xgwa);
  return xgwa.width;
}
  
int X11Window::Height (Display* dpy, Window window) {
  if (window == 0)
    window = DefaultRootWindow(dpy);
  XWindowAttributes xgwa;
  XGetWindowAttributes (dpy, window, &xgwa);
  return xgwa.height;
}

bool X11Window::Move (Display* dpy, Window window, int x, int y)
{
  XWindowChanges changes;
  changes.x = x;
  changes.y = y;
  
  XConfigureWindow (dpy, window, CWX | CWY, &changes);
  return true;
}

bool X11Window::Resize (Display* dpy, Window window, int w, int h)
{
  XWindowChanges changes;
  changes.width = w;
  changes.height = h;
  
  XConfigureWindow (dpy, window, CWWidth | CWHeight, &changes);
  return true;
}

int X11Window::Depth (Display* dpy, Window window) {
  if (window == 0)
    window = DefaultRootWindow(dpy);
  XWindowAttributes xgwa;
  XGetWindowAttributes (dpy, window, &xgwa);
  return xgwa.depth;
}
  
Visual* X11Window::ColorVisual (Display* dpy, Window window) {
  if (window == 0)
    window = DefaultRootWindow(dpy);
  XWindowAttributes xgwa;
  XGetWindowAttributes (dpy, window, &xgwa);
  return xgwa.visual;
}
  
Evas_Object* X11Window::CaptureIntoEvasImage (Evas* evas,
					      Display* dpy,
					      Window window,
					      int x, int y, int w, int h)
{
  // create the new Evas object ;-)
  Evas_Object* ob = evas_object_image_add(evas);
  evas_object_resize (ob, w, h);
    
  CaptureIntoEvasImage (ob, dpy, window, x, y, w, h);
    
  return ob;
}
  
void X11Window::CaptureIntoEvasImage (Evas_Object* ob,
				      Display* dpy,
				      Window window,
				      int x, int y, int w, int h)
{
  if (window == 0)
    window = DefaultRootWindow(dpy);
    
  XColor *colors = 0;
  
  // places content of a rectangle from drawable into an image
  XImage* ximage = XGetImage (dpy, window, x, y, w, h, ~0L, ZPixmap);
    
  // only needed to get valid r,g,b masks - I do not why ximage
  // does ont include those ...
  XImage* ximage2 =  XCreateImage (dpy, X11Window::ColorVisual (dpy, window),
				   32, ZPixmap, 0, 0,
				   w, h, 32, 0);
    
  // resize the Evas image to the proper size ...
  evas_object_image_size_set (ob, w, h); // !must be first!
  evas_object_image_fill_set(ob, 0, 0, w, h); // !and don't move me, too!
  uint8_t* data = (uint8_t*) evas_object_image_data_get (ob, 1);
    
  // Get Colormap
  Screen *dscreen = DefaultScreenOfDisplay (dpy);
  Visual *dvisual = DefaultVisualOfScreen (dscreen);
  if (X11Window::visual_class (dscreen, dvisual) == PseudoColor ||
      X11Window::visual_class (dscreen, dvisual) == GrayScale)
    {
      Colormap cmap = DefaultColormapOfScreen(dscreen);
      int ncolors = X11Window::visual_cells (dscreen, dvisual);
      int i;
	
      std::cout << "ncolors: " << ncolors << std::endl;
	
      colors = (XColor*) calloc (sizeof (*colors), ncolors+1);
      for (i = 0; i < ncolors; i++)
	colors[i].pixel = i;
      XQueryColors (dpy, cmap, colors, ncolors);
    }
    
  // Translate the server-ordered image to a client-ordered image.
  uint32_t srmsk = ximage2->red_mask;
  uint32_t sgmsk = ximage2->green_mask;
  uint32_t sbmsk = ximage2->blue_mask;
    
  if (!srmsk && !sgmsk && !sbmsk) {
    srmsk = 0xf800;
    sgmsk = 0x7e0;
    sbmsk = 0x1f;
  }
    
  int8_t srpos = first_bit_set (srmsk);
  int8_t sgpos = first_bit_set (sgmsk);
  int8_t sbpos = first_bit_set (sbmsk);
    
  int8_t srscl = 8 - bits_set (srmsk);
  int8_t sgscl = 8 - bits_set (sgmsk);
  int8_t sbscl = 8 - bits_set (sbmsk);
    
  uint32_t* data_ptr = (uint32_t*) data;
    
  for (int y = 0; y < h; ++y)
    for (int x = 0; x < w; ++x)
      {
	uint32_t sp = XGetPixel (ximage, x, y);
	uint8_t sr, sg, sb;
	  
	if (colors) {
	  sr = colors[sp].red;
	  sg = colors[sp].green;
	  sb = colors[sp].blue;
	}
	else {
	  sr = ((sp & srmsk) >> srpos) << srscl;
	  sg = ((sp & sgmsk) >> sgpos) << sgscl;
	  sb = ((sp & sbmsk) >> sbpos) << sbscl;
	}

	*data_ptr++ = 0xff << 24 | sr << 16 | sg << 8 | sb;
      }
    
  evas_object_image_data_update_add (ob, 0, 0, w, h); // !we got updated!
  evas_object_image_data_set (ob, data); // !and set the data! (?)
  
  if (colors) {
    free (colors);
    colors = 0;
  }
    
  XDestroyImage (ximage2);
  XDestroyImage (ximage);
}

int X11Window::screen_number (Screen *screen)
{
  Display *dpy = DisplayOfScreen (screen);
  int i;
  for (i = 0; i < ScreenCount (dpy); i++)
    if (ScreenOfDisplay (dpy, i) == screen)
      return i;
  std::cerr << "Error w/ ScreenOfDisplay" << std::endl;
  return 0;
}

int X11Window::visual_class (Screen *screen, Visual *visual)
{
  Display *dpy = DisplayOfScreen (screen);
  XVisualInfo vi_in, *vi_out;
  int out_count, c;
  vi_in.screen = screen_number (screen);
  vi_in.visualid = XVisualIDFromVisual (visual);
  vi_out = XGetVisualInfo (dpy, VisualScreenMask|VisualIDMask,
                           &vi_in, &out_count);
  if (! vi_out)
    std::cerr << "Error w/ XGetVisualInfo" << std::endl;
  c = vi_out [0].c_class; // hail to C ...
  XFree ((char *) vi_out);
  return c;
}

int X11Window::visual_cells (Screen *screen, Visual *visual)
{
  Display *dpy = DisplayOfScreen (screen);
  XVisualInfo vi_in, *vi_out;
  int out_count, c;
  vi_in.screen = screen_number (screen);
  vi_in.visualid = XVisualIDFromVisual (visual);
  vi_out = XGetVisualInfo (dpy, VisualScreenMask|VisualIDMask,
                           &vi_in, &out_count);
  if (! vi_out) abort ();
  c = vi_out [0].colormap_size;
  XFree ((char *) vi_out);
  return c;
}

// WindowStayOnTop code

void X11Window::StayOnTop (Display* dpy, Window win)
{
  Window root = DefaultRootWindow(dpy);
  
  if (!gnome_stay_on_top (dpy, root, win))
    netwm_stay_on_top (dpy, root, win);
  
  XRaiseWindow(dpy, win);
}

bool X11Window::gnome_stay_on_top (Display* dpy, Window root, Window win)
{
  Atom            type;
  int             format;
  unsigned long   nitems, bytesafter;
  unsigned char  *args = NULL;

  static Atom gnome;
  static Atom gnome_layer;
  
  const int WIN_LAYER_ONTOP = 6;
  
  XClientMessageEvent xev;
  
  gnome        = XInternAtom(dpy, "_WIN_SUPPORTING_WM_CHECK", False);
  gnome_layer  = XInternAtom(dpy, "_WIN_LAYER", False);
  
  // gnome compliant
  if (Success != XGetWindowProperty
      (dpy, root, gnome, 0, (65536 / sizeof(long)), False,
       AnyPropertyType, &type, &format, &nitems, &bytesafter, &args) ||
      nitems == 0) {
    std::cerr << "Error tring to stay on top the Gnome way." << std::endl;
    return false;
  }
  
  memset(&xev, 0, sizeof(xev));
  xev.type = ClientMessage;
  xev.window = win;
  xev.message_type = gnome_layer;
  xev.format = 32;
  xev.data.l[0] = WIN_LAYER_ONTOP;
  
  XSendEvent(dpy, root, False, SubstructureNotifyMask, (XEvent *)&xev);
  XFree(args);
  return true;
}

bool X11Window::netwm_stay_on_top (Display* dpy, Window root, Window win)
{
  Atom            type;
  int             format;
  unsigned long   nitems, bytesafter;
  unsigned char  *args = NULL;
  
  const int _NET_WM_STATE_ADD = 1; // add/set property
  static Atom net_wm;
  static Atom net_wm_state;
  static Atom net_wm_top;
  
  XEvent e;
  
  net_wm       = XInternAtom(dpy, "_NET_SUPPORTED", False);
  net_wm_state = XInternAtom(dpy, "_NET_WM_STATE", False);
  net_wm_top   = XInternAtom(dpy, "_NET_WM_STATE_STAYS_ON_TOP", False);
  
  // netwm compliant
  if (Success != XGetWindowProperty
      (dpy, root, net_wm, 0, (65536 / sizeof(long)), False,
       AnyPropertyType, &type, &format, &nitems, &bytesafter, &args) &&
      nitems > 0) {
    std::cerr << "Error tring to stay on top the Netwm way." << std::endl;
    return false;
  }

  e.xclient.type = ClientMessage;
  e.xclient.message_type = net_wm_state;
  e.xclient.display = dpy;
  e.xclient.window = win;
  e.xclient.format = 32;
  e.xclient.data.l[0] = _NET_WM_STATE_ADD;
  e.xclient.data.l[1] = net_wm_top;
  e.xclient.data.l[2] = 0l;
  e.xclient.data.l[3] = 0l;
  e.xclient.data.l[4] = 0l;
  
  XSendEvent(dpy, root, False, SubstructureRedirectMask, &e);
  XFree(args);
  return true;
}
