/** xcm.c
 *
 *  A X Color Management specification compatible information tool for server 
 *  based per window color management.
 *
 *  @par License: 
 *             MIT <http://www.opensource.org/licenses/mit-license.php>
 *  @par Copyright:
 *             (c) 2011-2013 Kai-Uwe Behrmann <ku.b@gmx.de>
 *
 *  gcc -Wall -g -I../.. xcm.c -o xcm `pkg-config --cflags --libs x11 xcm oyranos`
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h> /* sleep() */
#include <arpa/inet.h> /* ntohl() */

#include <X11/Xcm/Xcm.h>
#include <X11/Xcm/XcmEvents.h>
#include <X11/extensions/Xfixes.h> /* XserverRegion */
#include <X11/Xcm/Xcm.h> /* XcolorRegion */
#include <X11/Xutil.h>   /* NormalState */

#include "config.h"
#include "xcm_version.h"
#include "xcm_macros.h"
#define OY_DBG_FORMAT_ "%s:%d %s() "
#define OY_DBG_ARGS_ __FILE__,__LINE__,strrchr(__func__,'/')?strrchr(__func__,'/')+1:__func__

int XcolorRegionFind(XcolorRegion * old_regions, unsigned long old_regions_n, Display * dpy, Window win, XRectangle * rectangle);
void TraverseXTree( Display * dpy, Window win, Window root,
                    int print_window_name, int level);

void printfHelp(int argc, char ** argv)
{
  fprintf( stderr, "\n");
  fprintf( stderr, "%s %s\n",   argv[0],
                                _("is a X11 color management client tool"));
  fprintf( stderr, "  %s\n",
                 PACKAGE_STRING );
  fprintf( stderr, "\n");
  fprintf( stderr, "%s\n",                 _("Usage"));
  fprintf( stderr, "  %s\n",               _("List available windows:"));
  fprintf( stderr, "      %s -l [--window-name]\n",        argv[0]);
  fprintf( stderr, "        --window-name   %s\n", _("show window name"));
  fprintf( stderr, "\n");
  fprintf( stderr, "  %s\n",               _("Print infos about a window:"));
  fprintf( stderr, "      %s -p --id=window_id\n", argv[0]);
  fprintf( stderr, "\n");
  fprintf( stderr, "  %s\n",               _("Set window region:"));
  fprintf( stderr, "      %s -r\n",argv[0]);
  fprintf( stderr, "        [-x pos_x -y pos_y --width width --height height|\n"
                   "         --geometry width_x_height_+_x_+_y]\n");
  fprintf( stderr, "        --id=window_id [--profile filename.icc]\n");
  fprintf( stderr, "\n");
  fprintf( stderr, "  %s\n",               _("Release window region:"));
  fprintf( stderr, "      %s -d\n",argv[0]);
  fprintf( stderr, "        [-x pos_x -y pos_y --width width --height height|\n"
                   "         --geometry width_x_height_+_x_+_y]\n");
  fprintf( stderr, "        --id=window_id\n");
  fprintf( stderr, "\n");
  fprintf( stderr, "  %s\n",               _("Print a help text:"));
  fprintf( stderr, "      %s -h\n",        argv[0]);
  fprintf( stderr, "\n");
  fprintf( stderr, "  %s\n",               _("General options:"));
  fprintf( stderr, "        --display=:0.0  %s\n", _("X11 display name"));
  fprintf( stderr, "        -v              %s\n", _("verbose"));
  fprintf( stderr, "\n");
  fprintf( stderr, "\n");
}


int main(int argc, char ** argv)
{
  int id = 0;
  int print = 0;
  int print_window_name = 0;
  int list_windows = 0;
  const char * display = NULL;
  const char * geometry = NULL;

  Display * dpy = NULL;
  int screen = 0;
  Window win = 0,
         root;

  int place_region = 0;
  int release_region = 0;
  int x = 0, y = 0, width = 0, height = 0;
  const char * profile_name = NULL;
  int verbose = 0;
  int result = 0;

  XserverRegion reg = 0;
  XcolorRegion region;
  int error;
  XRectangle rec[2] = { { 0,0,0,0 }, { 0,0,0,0 } };

#ifdef XCM_HAVE_OY
  char * blob = 0;
  size_t size = 0;
  oyProfile_s * p = 0;
  XcolorProfile * profile = 0;
#endif

#ifdef USE_GETTEXT
  setlocale(LC_ALL,"");
#endif

  if(argc >= 2)
  {
    int pos = 1, i;
    char *wrong_arg = 0;
    while(pos < argc)
    {
      switch(argv[pos][0])
      {
        case '-':
            for(i = 1; pos < argc && i < strlen(argv[pos]); ++i)
            switch (argv[pos][i])
            {
              case 'd': release_region = 1; break;
              case 'l': list_windows = 1; break;
              case 'p': print = 1; break;
              case 'r': place_region = 1; break;
              case 'v': verbose += 1; break;
              case 'x': OY_PARSE_INT_ARG( x ); break;
              case 'y': OY_PARSE_INT_ARG( y ); break;
              case 'h':
              case '-':
                        if(i == 1)
                        {
                             if(OY_IS_ARG("id"))
                        { OY_PARSE_INT_ARG2( id, "id" ); break; }
                        else if(OY_IS_ARG("width"))
                        { OY_PARSE_INT_ARG2( width, "width" ); break; }
                        else if(OY_IS_ARG("height"))
                        { OY_PARSE_INT_ARG2( height, "height" ); break; }
                        else if(OY_IS_ARG("profile"))
                        { OY_PARSE_STRING_ARG2( profile_name, "profile" ); break; }
                        else if(OY_IS_ARG("display"))
                        { OY_PARSE_STRING_ARG2( display, "display" ); break; }
                        else if(OY_IS_ARG("geometry"))
                        { OY_PARSE_STRING_ARG2( geometry, "geometry" ); break; }
                        else if(OY_IS_ARG("window-name"))
                        { print_window_name = 1; i=100; break; }
                        }
              default:
                        printfHelp(argc, argv);
                        exit (0);
                        break;
            }
            break;
        default:
                        printfHelp(argc, argv);
                        exit (0);
                        break;
      }
      if( wrong_arg )
      {
       fprintf(stderr, "%s %s\n", _("wrong argument to option:"), wrong_arg);
       printfHelp(argc, argv);
       exit(1);
      }
      ++pos;
    }
  } else
  {
                        printfHelp(argc, argv);
                        exit (0);
  }

#ifdef XCM_HAVE_OY
  XcmICCprofileFromMD5FuncSet( fromMD5 );
  XcmICCprofileGetNameFuncSet( getName );
#endif

  dpy = XOpenDisplay( display );
  if(!dpy)
  {
    fprintf( stderr, "%s %s\n", "unable to open display", display?display:"");
    exit(1);
  }

  if(geometry)
  {
    int matches = sscanf( geometry, "%ix%i+%i+%i",
                          &width, &height, &x, &y );
    if(matches !=  4 && strcmp(geometry,"0x0+0+0") != 0)
    {
      fprintf( stderr, "%s: --geometry WIDTHxHEIGHT+XPOS+YPOS (%s)\n",
               _("argument not recognised"), geometry);
      exit(1);
    }
  }

  /* copy geometry taken by --geometry or othe rarguments */
  rec[0].x = x;
  rec[0].y = y;
  rec[0].width = width;
  rec[0].height = height;

  win = (Window) id;
  screen = DefaultScreen( dpy );
  root = XRootWindow( dpy, screen );

  if(list_windows)
  {
    if(!verbose)
    {
      XSync( dpy, 0 );
      TraverseXTree( dpy, root, root, print_window_name, 0 );
    } else
    {
      XcmeContext_s * c = XcmeContext_Create( display );
      XcmeContext_Release( &c );
    }

  } else if(print)
  {
    if(!win)
    {
      fprintf( stderr, "%s\n", _("The integer window ID is missed. Use option: --id 12345"));
      exit(1);
    }
    fprintf( stdout, "%s\n", XcmePrintWindowRegions( dpy, win, 1 ) );
  } else if(place_region)
  {
    int need_wait = 1;
#ifdef XCM_HAVE_OY
    /* Upload a ICC profile to X11 root window */
    if(profile_name)
    {
      p = oyProfile_FromFile( profile_name, 0,0 );
      if(p)
      {
        blob = oyProfile_GetMem( p, &size, 0,0 );

        if(blob && size)
        {
        /* Create a XcolorProfile object that will be uploaded to the display.*/
          profile = malloc(sizeof(XcolorProfile) + size);

          oyProfile_GetMD5(p, OY_FROM_PROFILE, (uint32_t*)profile->md5);

          profile->length = htonl(size);
          memcpy(profile + 1, blob, size);

          result = XcolorProfileUpload( dpy, profile );
          if(result)
            printf("XcolorProfileUpload: %d\n", result);
        }
        oyProfile_Release( &p );
      }
    }
#endif

    reg = XFixesCreateRegion( dpy, rec, 1);

    region.region = htonl(reg);
#ifdef XCM_HAVE_OY
    if(blob && size)
      memcpy(region.md5, profile->md5, 16);
    else
#endif
      memset( region.md5, 0, 16 );

    if(rec[0].x || rec[0].y || rec[0].width || rec[0].height)
    {

      /* upload the new or changed region to the X server */
      error = XcolorRegionInsert( dpy, win, 0, &region, 1 );
      if(error)
          printf( OY_DBG_FORMAT_
                   "XcolorRegionInsert failed %d\n",
                   OY_DBG_ARGS_, error );

    } else
    {
      unsigned long nRegions = 0;
      XcolorRegion * r = XcolorRegionFetch( dpy, win, &nRegions );
      need_wait = 0;
      if(nRegions && r)
      {
        error = XcolorRegionDelete( dpy, win, 0, nRegions );
        fprintf(stderr, "released %lu region%c\n", nRegions, nRegions==1?' ':'s');
        XFree( r ); r = 0;
      } else
      {
        fprintf(stderr, "no region to release \n");
      }
    }

    XFlush( dpy );

    /** Closing the display object will destroy all XFixes regions automatically
     *  by Xorg. Therefore we loop here to keep the XFixes regions alive. */
    if(need_wait)
      while(1) sleep(2);

  } if(release_region)
  {
    XcolorRegion *old_regions = 0;
    unsigned long old_regions_n = 0;
    int pos = -1;

    /* get old regions */
    old_regions = XcolorRegionFetch( dpy, win, &old_regions_n );
    /* remove specified region */
    pos = XcolorRegionFind( old_regions, old_regions_n, dpy, win, rec );
    XFree( old_regions );
    if(pos >= 0)
    {
      int unreleased_n = old_regions_n;
      XcolorRegionDelete( dpy, win, pos, 1 );
      old_regions = XcolorRegionFetch( dpy, win, &old_regions_n );
      if(unreleased_n - old_regions_n != 1)
        printf(  OY_DBG_FORMAT_"removed %d; have still %d\n", OY_DBG_ARGS_,
                 pos, (int)old_regions_n );
      else
        fprintf( stderr, "removed position %d\n", pos );
    } else
      printf( "region not found: %s in %lu\n", geometry, old_regions_n );

    XFlush( dpy );

  }

  XCloseDisplay( dpy );

  return result;
}

int XcolorRegionFind(XcolorRegion * old_regions, unsigned long old_regions_n, Display * dpy, Window win, XRectangle * rectangle)
{
  XRectangle * rect = 0;
  int nRect = 0;
  int pos = -1;
  int i, j;

  /* get old regions */
  old_regions = XcolorRegionFetch( dpy, win, &old_regions_n );
  /* search region */
  for(i = 0; i < old_regions_n; ++i)
  {

    if(!old_regions[i].region || pos >= 0)
      break;

    rect = XFixesFetchRegion( dpy, ntohl(old_regions[i].region),
                              &nRect );

    for(j = 0; j < nRect; ++j)
    {
#ifdef XCM_HAVE_OY
      if(oy_debug)
        printf( OY_DBG_FORMAT_
                 "reg[%d]: %dx%d+%d+%d %dx%d+%d+%d\n",
                   OY_DBG_ARGS_, i,
                   rectangle->width, rectangle->height,
                   rectangle->x, rectangle->y,
                   rect[j].width, rect[j].height, rect[j].x, rect[j].y
                  );
#endif
      if(rectangle->x == rect[j].x &&
         rectangle->y == rect[j].y &&
         rectangle->width == rect[j].width &&
         rectangle->height == rect[j].height )
      {
        pos = i;
        break;
      }
    }
  }

  return pos;
}

void TraverseXTree( Display * dpy, Window win, Window root,
                    int print_window_name, int level)
{
  Window root_return = 0,
         parent_return = 0, 
       * wins = 0;
  unsigned int wins_n = 0;
  int i;
  XWindowAttributes window_attributes_return;
  Status status = 0;

  Atom atom_wm_state;
  Atom atom_type = None;
  int format;
  unsigned long nitems = 0, after;
  unsigned char *data = NULL;
  Window inf;

  unsigned long nRegions = 0;
  XcolorRegion * r = XcolorRegionFetch( dpy, win, &nRegions );

  status = XQueryTree( dpy, win,
                       &root_return, &parent_return,
                       &wins, &wins_n );
  if(status == 0) fprintf( stderr, "%d: XQueryTree failed\n", __LINE__ );
  status = XGetWindowAttributes( dpy, win,
                                 &window_attributes_return );
  if(status == 0) fprintf( stderr,"%d: XGetWindowAttributes failed\n",__LINE__);

  atom_wm_state = XInternAtom(dpy, "WM_STATE", True);
  if(atom_wm_state)
    XGetWindowProperty(dpy, win, atom_wm_state, 0, 2L, False, AnyPropertyType,
                       &atom_type, &format, &nitems, &after, &data);

  if(window_attributes_return.map_state == IsViewable &&
     root_return == root && nitems && (*(uint32_t*)data) == NormalState)
  {
    int j;
    for(j = 0; j < level; ++j)
      fprintf( stdout, "  " );
    fprintf( stdout, "%d", (int)win );
    if(print_window_name)
    {
      fprintf( stdout, "  %s",
               XcmePrintWindowName(dpy, win) );
      if(nRegions)
        fprintf( stdout, " %lu", nRegions );
    }
    fprintf( stdout, "\n" );
  }

  for(i = 0; i < wins_n; ++i)
    TraverseXTree( dpy, wins[i], root, print_window_name, level+1 );

  if(wins)
    XFree( wins );
  if(data)
    XFree( data );
  if(r)
    XFree( r );
}

#ifdef XCM_HAVE_OY
void * fromMD5                       ( const void        * md5_hash,
                                       size_t            * size,
                                       void              *(allocate_func)(size_t) )
{
  void * data = 0;
  oyProfile_s * p = oyProfile_FromMD5( (uint32_t*)md5_hash, 0, 0 );
  data = oyProfile_GetMem( p, size, 0, allocate_func );
  oyProfile_Release( &p );
  return data;
}

char * getName                       ( const void        * data,
                                       size_t              size,
                                       void              *(allocate_func)(size_t),
                                       int                 file_name )
{
  char * text = 0;
  const char * t = 0;
  oyProfile_s * p = oyProfile_FromMem( size, (void*)data, 0, 0 );
  if(file_name)
    t = oyProfile_GetFileName( p, -1 );
  else
    t = oyProfile_GetText( p, oyNAME_DESCRIPTION );

  if(t && t[0])
  {
    text = (char*)allocate_func( strlen(t) + 1 );
    strcpy( text, t );
  }

  oyProfile_Release( &p );
  return text;
}
#endif
