#include "unix/guts.h"
#include "Application.h"
#include "Clipboard.h"
#include "Icon.h"

#define WIN PComponent(application)-> handle

#define CF_NAME(x)   (guts. clipboard_formats[(x)*3])
#define CF_TYPE(x)   (guts. clipboard_formats[(x)*3+1])
#define CF_FORMAT(x) (guts. clipboard_formats[(x)*3+2])
#define CF_ASSIGN(i,a,b,c) CF_NAME(i)=(a);CF_TYPE(i)=(b);CF_FORMAT(i)=((Atom)c)
#define CF_32        (sizeof(long)*8)        /* 32-bit properties are hacky */

Bool
prima_init_clipboard_subsystem(char * error_buf)
{
   guts. clipboards = hash_create();
   
   if ( !(guts. clipboard_formats = malloc( cfCOUNT * 3 * sizeof(Atom)))) {
      sprintf( error_buf, "No memory");
      return false;
   }
   guts. clipboard_formats_count = cfCOUNT;
#if (cfText != 0) || (cfBitmap != 1) || (cfUTF8 != 2)
#error broken clipboard type formats
#endif  

   CF_ASSIGN(cfText, XA_STRING, XA_STRING, 8);
   CF_ASSIGN(cfUTF8, UTF8_STRING, UTF8_STRING, 8);
   CF_ASSIGN(cfBitmap, XA_PIXMAP, XA_PIXMAP, CF_32);
   CF_ASSIGN(cfTargets, CF_TARGETS, XA_ATOM, CF_32);

   /* XXX - bitmaps and indexed pixmaps may have the associated colormap or pixel values 
   CF_ASSIGN(cfPalette, XA_COLORMAP, XA_ATOM, CF_32);
   CF_ASSIGN(cfForeground, CF_FOREGROUND, CF_PIXEL, CF_32);
   CF_ASSIGN(cfBackground, CF_BACKGROUND, CF_PIXEL, CF_32);
   */
   
   guts. clipboard_event_timeout = 2000;
   return true;
}

PList
apc_get_standard_clipboards( void)
{
   PList l = plist_create( 3, 1);
   if (!l) return nil;
   list_add( l, (Handle)duplicate_string( "Primary"));
   list_add( l, (Handle)duplicate_string( "Secondary"));
   list_add( l, (Handle)duplicate_string( "Clipboard"));
   return l;
}

Bool
apc_clipboard_create( Handle self)
{
   PClipboard c = (PClipboard)self;
   char *name, *x;
   DEFCC;

   XX-> selection = None;
   
   name = x = duplicate_string( c-> name);
   while (*x) {
      *x = toupper(*x);
      x++;
   }
   XX-> selection = XInternAtom( DISP, name, false);
   free( name);

   if ( hash_fetch( guts.clipboards, &XX->selection, sizeof(XX->selection))) {
      warn("This clipboard is already present");
      return false;
   }

   if ( !( XX-> internal = malloc( sizeof( ClipboardDataItem) * cfCOUNT))) {
      warn("Not enough memory");
      return false;
   }
   if ( !( XX-> external = malloc( sizeof( ClipboardDataItem) * cfCOUNT))) {
      free( XX-> internal);
      warn("Not enough memory");
      return false;
   }
   bzero( XX-> internal, sizeof( ClipboardDataItem) * cfCOUNT);
   bzero( XX-> external, sizeof( ClipboardDataItem) * cfCOUNT);

   hash_store( guts.clipboards, &XX->selection, sizeof(XX->selection), (void*)self);

   return true;
}

static void
clipboard_free_data( void * data, int size, Handle id)
{
   if ( size <= 0) {
      if ( size == 0 && data != nil) free( data);
      return;
   }
   if ( id == cfBitmap) {
      int i;
      Pixmap * p = (Pixmap*) data;
      for ( i = 0; i < size/sizeof(Pixmap); i++, p++)
         if ( *p)
            XFreePixmap( DISP, *p);
   }
   free( data);
}

/*
   each clipboard type can be represented by a set of 
   X properties pairs, where each is X name and X type.
   get_typename() returns such pairs by the index.
 */
static Atom
get_typename( Handle id, int index, Atom * type)
{
   if ( type) *type = None;
   switch ( id) {
   case cfUTF8:
      if ( index > 1) return None;
      if ( index == 0) {
         if ( type) *type = CF_TYPE(id);
         return CF_NAME(id);
      } else {
         if ( type) *type = UTF8_MIME;
         return UTF8_MIME;
      }
   case cfBitmap:
      if ( index > 1) return None;
      if ( index == 0) {
         if ( type) *type = CF_TYPE(id);
	 return CF_NAME(id);
      } else {
         if ( type) *type = XA_BITMAP;
	 return XA_BITMAP;
      }
   case cfTargets:
      if ( index > 1) return None;
      if ( index == 0) {
         if ( type) *type = CF_TYPE(id);
         return CF_NAME(id);
      } else {
         if ( type) *type = CF_TARGETS;
         return CF_NAME(id);
      }
   }
   if ( index > 0) return None;
   if ( type) *type = CF_TYPE(id);
   return CF_NAME(id);
}

static void
clipboard_kill_item( PClipboardDataItem item, Handle id)
{
   item += id;
   clipboard_free_data( item-> data, item-> size, id);
   item-> data = nil;
   item-> size = 0;
   item-> name = get_typename( id, 0, nil);
}

/*
   Deletes a transfer record from pending xfer chain.
 */
static void
delete_xfer( PClipboardSysData cc, ClipboardXfer * xfer)
{
   ClipboardXferKey key;
   CLIPBOARD_XFER_KEY( key, xfer-> requestor, xfer-> property);
   if ( guts. clipboard_xfers) {
      IV refcnt;
      hash_delete( guts. clipboard_xfers, key, sizeof( key), false);
      refcnt = PTR2IV( hash_fetch( guts. clipboard_xfers, &xfer-> requestor, sizeof(XWindow)));
      if ( --refcnt == 0) {
         XSelectInput( DISP, xfer-> requestor, 0);
         hash_delete( guts. clipboard_xfers, &xfer-> requestor, sizeof(XWindow), false);
      } else {
         if ( refcnt < 0) refcnt = 0;
         hash_store( guts. clipboard_xfers, &xfer-> requestor, sizeof(XWindow), INT2PTR(void*, refcnt));
      }
   }
   if ( cc-> xfers) 
      list_delete( cc-> xfers, ( Handle) xfer);
   if ( xfer-> data_detached && xfer-> data_master) 
      clipboard_free_data( xfer-> data, xfer-> size, xfer-> id);
   free( xfer);
}

Bool
apc_clipboard_destroy( Handle self)
{
   DEFCC;
   int i;

   if (XX-> selection == None) return true;

   if ( XX-> xfers) {
      for ( i = 0; i < XX-> xfers-> count; i++) 
         delete_xfer( XX, ( ClipboardXfer*) XX-> xfers-> items[i]);
      plist_destroy( XX-> xfers);
   }

   for ( i = 0; i < guts. clipboard_formats_count; i++) {
      if ( XX-> external) clipboard_kill_item( XX-> external, i);
      if ( XX-> internal) clipboard_kill_item( XX-> internal, i);
   }

   free( XX-> external);
   free( XX-> internal);
   hash_delete( guts.clipboards, &XX->selection, sizeof(XX->selection), false);

   XX-> selection = None;
   return true;
}

Bool
apc_clipboard_open( Handle self)
{
   DEFCC;
   if ( XX-> opened) return false;
   XX-> opened = true;
   
   if ( !XX-> inside_event) XX-> need_write = false;

   return true;
}

Bool
apc_clipboard_close( Handle self)
{
   DEFCC;
   if ( !XX-> opened) return false;
   XX-> opened = false;

   /* check if UTF8 is present and Text is not, and downgrade */
   if ( XX-> need_write &&
	XX-> internal[cfUTF8]. size > 0 &&
	XX-> internal[cfText]. size == 0) {
      Byte * src = XX-> internal[cfUTF8]. data;
      int len    = utf8_length( src, src + XX-> internal[cfUTF8]. size);
      if (( XX-> internal[cfText]. data = malloc( len))) {
	  STRLEN charlen;
	  U8 *dst;
	  dst = XX-> internal[cfText]. data;
          XX-> internal[cfText]. size = len;
	  while ( len--) {
             register UV u = 
#if PERL_PATCHLEVEL >= 16
	     utf8_to_uvchr_buf( src, src + XX-> internal[cfUTF8]. size, &charlen);
#else
	     utf8_to_uvchr( src, &charlen)
#endif
	     ;
	     *(dst++) = ( u < 0x7f) ? u : '?'; /* XXX employ $LANG and iconv() */
	     src += charlen;
	  }
      } 
   }
      

   if ( !XX-> inside_event) {
      int i;         
      for ( i = 0; i < guts. clipboard_formats_count; i++) 
         clipboard_kill_item( XX-> external, i);
      if ( XX-> need_write) 
         if ( XGetSelectionOwner( DISP, XX-> selection) != WIN) 
            XSetSelectionOwner( DISP, XX-> selection, WIN, CurrentTime);
   }
   
   return true;
}

/*
   Detaches data for pending transfers from XX, so eventual changes 
   to XX->internal would not affect them. detach_xfers() should be
   called before clipboard_kill_item(XX-> internal), otherwise
   there's a chance of coredump.
 */
static void
detach_xfers( PClipboardSysData XX, Handle id, Bool clear_original_data)
{
   int i, got_master = 0, got_anything = 0;
   if ( !XX-> xfers) return;
   for ( i = 0; i < XX-> xfers-> count; i++) {
      ClipboardXfer * x = ( ClipboardXfer *) XX-> xfers-> items[i];
      if ( x-> data_detached || x-> id != id) continue;
      got_anything = 1;
      if ( !got_master) {
         x-> data_master = true;
         got_master = 1;
      }
      x-> data_detached = true;
   }   
   if ( got_anything && clear_original_data) {
      XX-> internal[id]. data = nil;
      XX-> internal[id]. size = 0;
      XX-> internal[id]. name = get_typename( id, 0, nil);
   }
}

Bool
apc_clipboard_clear( Handle self)
{
   DEFCC;
   int i;

   for ( i = 0; i < guts. clipboard_formats_count; i++) {
      detach_xfers( XX, i, true);
      clipboard_kill_item( XX-> internal, i);
      clipboard_kill_item( XX-> external, i);
   }
   
   if ( XX-> inside_event) { 
     XX-> need_write = true; 
   } else {
     XWindow owner = XGetSelectionOwner( DISP, XX-> selection);
     XX-> need_write = false;
     if ( owner != None && owner != WIN)
        XSetSelectionOwner( DISP, XX-> selection, None, CurrentTime);
   }

   return true;
}

typedef struct {
   Atom selection;
   long mask;
} SelectionProcData;

#define SELECTION_NOTIFY_MASK 1
#define PROPERTY_NOTIFY_MASK  2

static int
selection_filter( Display * disp, XEvent * ev, SelectionProcData * data)
{
   switch ( ev-> type) {
   case PropertyNotify:
      return (data-> mask & PROPERTY_NOTIFY_MASK) && (data-> selection == ev-> xproperty. atom);
   case SelectionRequest:
   case SelectionClear:
   case MappingNotify:
      return true;
   case SelectionNotify:
      return (data-> mask & SELECTION_NOTIFY_MASK) && (data-> selection == ev-> xselection. selection);
   case ClientMessage:
      if ( ev-> xclient. window == WIN ||
           ev-> xclient. window == guts. root ||
           ev-> xclient. window == None) return true;
      if ( hash_fetch( guts.windows, (void*)&ev-> xclient. window, 
           sizeof(ev-> xclient. window))) return false;
      return true;
   }
   return false;
}

#define CFDATA_NONE            0
#define CFDATA_NOT_ACQUIRED  (-1)
#define CFDATA_ERROR         (-2)

#define RPS_OK       0
#define RPS_PARTIAL  1
#define RPS_NODATA   2
#define RPS_ERROR    3

static int
read_property( Atom property, Atom * type, int * format, 
               unsigned long * size, unsigned char ** data)
{
   int ret = ( *size > 0) ? RPS_PARTIAL : RPS_ERROR;
   unsigned char * prop, *a1;
   unsigned long n, left, offs = 0, new_size, big_offs = *size;

   XCHECKPOINT;
   Cdebug("clipboard: read_property: %s\n", XGetAtomName(DISP, property));
   while ( 1) {
      if ( XGetWindowProperty( DISP, WIN, property,
          offs, guts. limits. request_length - 4, false, 
          AnyPropertyType, 
          type, format, &n, &left, &prop) != Success) {
         XDeleteProperty( DISP, WIN, property);
	 Cdebug("clipboard:fail\n");
         return ret;
      }
      XCHECKPOINT;
      Cdebug("clipboard: type=0x%x(%s) fmt=%d n=%d left=%d\n", 
	     *type, XGetAtomName(DISP,*type), *format, n, left);
      
      if ( *format == 32) *format = CF_32;

      if ( *type == 0 ) return RPS_NODATA;

      new_size = n * *format / 8;

      if ( new_size > 0) {
         if ( !( a1 = realloc( *data, big_offs + offs * 4 + new_size))) {
            warn("Not enough memory: %ld bytes\n", offs * 4 + new_size);
            XDeleteProperty( DISP, WIN, property);
            XFree( prop);
            return ret;
         }
         *data = a1;
         memcpy( *data + big_offs + offs * 4, prop, new_size);
         *size = big_offs + (offs * 4) + new_size;
         if ( *size > INT_MAX) *size = INT_MAX;
         offs += new_size / 4;
         ret = RPS_PARTIAL;
      }
      XFree( prop);
      if ( left <= 0 || *size == INT_MAX || n * *format == 0) break;
   }

   XDeleteProperty( DISP, WIN, property);
   XCHECKPOINT;

   return RPS_OK;
}

static Bool
query_datum( Handle self, Handle id, Atom query_target, Atom query_type)
{
   DEFCC;
   XEvent ev;
   Atom type;
   int format, rps;
   SelectionProcData spd;
   unsigned long size = 0, incr = 0, old_size, delay;
   unsigned char * data;
   struct timeval start_time, timeout;
 
   /* init */
   if ( query_target == None) return false;
   data = malloc(0);
   XX-> external[id]. size = CFDATA_ERROR;
   gettimeofday( &start_time, nil);
   XCHECKPOINT;
   Cdebug("clipboard:convert %s from %08x\n", XGetAtomName( DISP, query_target), WIN);
   XDeleteProperty( DISP, WIN, XX-> selection);
   XConvertSelection( DISP, XX-> selection, query_target, XX-> selection, WIN, guts. last_time);
   XFlush( DISP);
   XCHECKPOINT;

   /* wait for SelectionNotify */
   spd. selection = XX-> selection;
   spd. mask = SELECTION_NOTIFY_MASK;
   while ( 1) {
      XIfEvent( DISP, &ev, (XIfEventProcType)selection_filter, (char*)&spd);
      if ( ev. type != SelectionNotify) {
         prima_handle_event( &ev, nil);
         continue;
      }
      if ( ev. xselection. property == None) goto FAIL;
      Cdebug("clipboard:read SelectionNotify  %s %s\n",
             XGetAtomName(DISP, ev. xselection. property),
             XGetAtomName(DISP, ev. xselection. target));
      gettimeofday( &timeout, nil);
      delay = 2 * (( timeout. tv_sec - start_time. tv_sec) * 1000 + 
                   ( timeout. tv_usec - start_time. tv_usec) / 1000) + guts. clipboard_event_timeout;
      start_time = timeout;
      if ( read_property( ev. xselection. property, &type, &format, &size, &data) > RPS_PARTIAL) 
         goto FAIL;
      XFlush( DISP);
      break;
   }
   XCHECKPOINT;

   if ( type != XA_INCR) { /* ordinary, single-property selection */
      if ( format != CF_FORMAT(id) || type != query_type) {
	 if ( format != CF_FORMAT(id)) 
	    Cdebug("clipboard: id=%d: formats mismatch: got %d, want %d\n", id, format, CF_FORMAT(id));
	 if ( type != query_type) 
	    Cdebug("clipboard: id=%d: types mismatch: got %s, want %s\n", id,
		   XGetAtomName(DISP,type), XGetAtomName(DISP,query_type));
	 return false;
      }
      XX-> external[id]. size = size;
      XX-> external[id]. data = data;
      XX-> external[id]. name = query_target;
      return true;
   }

   /* setup INCR */
   if ( format != CF_32 || size < 4) goto FAIL;
   incr = (unsigned long) *(( Atom*) data);
   if ( incr == 0) goto FAIL;
   size = 0;
   spd. mask = PROPERTY_NOTIFY_MASK;

   while ( 1) {
      /* wait for PropertyNotify */ 
      while ( XCheckIfEvent( DISP, &ev, (XIfEventProcType)selection_filter, (char*)&spd) == False) {
         gettimeofday( &timeout, nil);
         if ((( timeout. tv_sec - start_time. tv_sec) * 1000 + 
              ( timeout. tv_usec - start_time. tv_usec) / 1000) > delay) 
              goto END_LOOP;
      }
      if ( ev. type != PropertyNotify) {
         prima_handle_event( &ev, nil);
         continue;
      }
      if ( ev. xproperty. state != PropertyNewValue) continue;
      start_time = timeout;
      old_size = size;

      rps = read_property( ev. xproperty. atom, &type, &format, &size, &data);
      XFlush( DISP);
      if ( rps == RPS_NODATA) continue;
      if ( rps == RPS_ERROR) goto FAIL;         
      if ( format != CF_FORMAT(id) || type != CF_TYPE(id)) return false;
      if ( size > incr ||                       /* read all in INCR */
           rps == RPS_PARTIAL ||                /* failed somewhere */
           ( size == incr && old_size == size)  /* wait for empty PropertyNotify otherwise */
           ) break;
   }
END_LOOP:
   XCHECKPOINT;

   XX-> external[id]. size   = size;
   XX-> external[id]. data   = data;
   XX-> external[id]. name   = query_target;
   return true;
   
FAIL:
   XCHECKPOINT;
   free( data);
   return false;
}


static Bool
query_data( Handle self, Handle id)
{
   Atom name, type;
   int index = 0;
   while (( name = get_typename( id, index++, &type)) != None) {
      if ( query_datum( self, id, name, type)) return true;
   }
   return false;
}

static Atom
find_atoms( Atom * data, int length, int id)
{
   int i, index = 0;
   Atom name;
   
   while (( name = get_typename( id, index++, nil)) != None) {
      for ( i = 0; i < length / sizeof(Atom); i++) {
         if ( data[i] == name) 
            return name;
      }
   }
   return None;
}


Bool
apc_clipboard_has_format( Handle self, Handle id)
{
   DEFCC;
   if ( id >= guts. clipboard_formats_count) return false;

   if ( XX-> inside_event) {
      return XX-> internal[id]. size > 0 || XX-> external[id]. size > 0;
   } else {
      if ( XX-> internal[id]. size > 0) return true;

      if ( XX-> external[cfTargets]. size == 0) {
         /* read TARGETS, which as array of ATOMs */
         query_data( self, cfTargets);

         if ( XX-> external[cfTargets].size > 0) {
            int i, size = XX-> external[cfTargets].size;
            Atom * data = ( Atom*)(XX-> external[cfTargets]. data);
            Atom ret;

            
            Cdebug("clipboard targets:");
            for ( i = 0; i < size/4; i++) 
               Cdebug("%s\n", XGetAtomName( DISP, data[i]));

            /* find our index for TARGETS[i], assign CFDATA_NOT_ACQUIRED to it */
            for ( i = 0; i < guts. clipboard_formats_count; i++) {
               if ( i == cfTargets) continue;
               ret = find_atoms( data, size, i);
               if ( ret != None && (
                      XX-> external[i]. size == 0 ||
                      XX-> external[i]. size == CFDATA_ERROR
                    )
                  ) { 
                   XX-> external[i]. size = CFDATA_NOT_ACQUIRED;
                   XX-> external[i]. name = ret;
                }
            }

            if ( XX-> external[id]. size == 0 || 
                 XX-> external[id]. size == CFDATA_ERROR)
               return false;
         }
      }
      
      if ( XX-> external[id]. size > 0 || 
           XX-> external[id]. size == CFDATA_NOT_ACQUIRED)
         return true;

      if ( XX-> external[id]. size == CFDATA_ERROR) 
         return false;

      /* selection owner does not support TARGETS, so peek */
      if ( XX-> external[cfTargets]. size == 0 && XX-> internal[id]. size == 0)
         return query_data( self, id);
   }
   return false;
}

Bool
apc_clipboard_get_data( Handle self, Handle id, PClipboardDataRec c)
{
   DEFCC;
   STRLEN size;
   unsigned char * data;
   Atom name;

   if ( id >= guts. clipboard_formats_count) return false;

   if ( !XX-> inside_event) {
      if ( XX-> internal[id]. size == 0) {
         if ( XX-> external[id]. size == CFDATA_NOT_ACQUIRED) {
            if ( !query_data( self, id)) return false;
         }
         if ( XX-> external[id]. size == CFDATA_ERROR) return false;
      }
   }
   if ( XX-> internal[id]. size == CFDATA_ERROR) return false;

   if ( XX-> internal[id]. size > 0) {
      size = XX-> internal[id]. size;
      data = XX-> internal[id]. data;
      name = XX-> internal[id]. name;
   } else {
      size = XX-> external[id]. size;
      data = XX-> external[id]. data;
      name = XX-> external[id]. name;
   }
   if ( size == 0 || data == nil) return false;

   switch ( id) {
   case cfBitmap: {
      Handle img = c-> image; 
      XWindow foo;
      Pixmap px = *(( Pixmap*)( data));
      unsigned int dummy, x, y, d;
      int bar;
     
      if ( !XGetGeometry( DISP, px, &foo, &bar, &bar, &x, &y, &dummy, &d))
         return false;
      CImage( img)-> create_empty( img, x, y, ( d == 1) ? imBW : guts. qdepth);
      if ( !prima_std_query_image( img, px)) return false;
      break;}
   case cfText:
   case cfUTF8: {
      void * ret = malloc( size);
      if ( !ret) {
         warn("Not enough memory: %d bytes\n", (int)size);
         return false;
      }
      memcpy( ret, data, size);
      c-> data   = ret;
      c-> length = size;
      break;}
   default: {
      void * ret = malloc( size);
      if ( !ret) {
         warn("Not enough memory: %d bytes\n", (int)size);
         return false;
      }
      memcpy( ret, data, size);
      c-> data = ( Byte * ) ret;
      c-> length = size;
      break;}
   }
   return true;
}

Bool
apc_clipboard_set_data( Handle self, Handle id, PClipboardDataRec c)
{
   DEFCC;
   if ( id >= guts. clipboard_formats_count) return false;

   if ( id >= cfTargets && id < cfCOUNT ) return false;
   detach_xfers( XX, id, true);
   clipboard_kill_item( XX-> internal, id);

   switch ( id) {
   case cfBitmap: {  
      Pixmap px = prima_std_pixmap( c-> image, CACHE_LOW_RES);
      if ( px) {
         if ( !( XX-> internal[cfBitmap]. data = malloc( sizeof( px)))) {
            XFreePixmap( DISP, px);
            return false;
         }
         XX-> internal[cfBitmap]. size = sizeof(px);
         memcpy( XX-> internal[cfBitmap]. data, &px, sizeof(px));
      } else
         return false;
      break;}
   default:
      if ( !( XX-> internal[id]. data = malloc( c-> length))) 
         return false;
      XX-> internal[id]. size = c-> length;
      memcpy( XX-> internal[id]. data, c-> data, c-> length);
      break;
   }
   XX-> need_write = true; 
   return true;
}

static Bool
expand_clipboards( Handle self, int keyLen, void * key, void * dummy)
{
   DEFCC;
   PClipboardDataItem f;

   if ( !( f = realloc( XX-> internal, 
      sizeof( ClipboardDataItem) * guts. clipboard_formats_count))) {
      guts. clipboard_formats_count--;
      return true;
   }
   f[ guts. clipboard_formats_count-1].size = 0;
   f[ guts. clipboard_formats_count-1].data = nil;
   f[ guts. clipboard_formats_count-1].name = CF_NAME(guts. clipboard_formats_count-1);
   XX-> internal = f;
   if ( !( f = realloc( XX-> external, 
      sizeof( ClipboardDataItem) * guts. clipboard_formats_count))) {
      guts. clipboard_formats_count--;
      return true;
   }
   f[ guts. clipboard_formats_count-1].size = 0;
   f[ guts. clipboard_formats_count-1].data = nil;
   f[ guts. clipboard_formats_count-1].name = CF_NAME(guts. clipboard_formats_count-1);
   XX-> external = f;
   return false;
}

Handle
apc_clipboard_register_format( Handle self, const char* format)
{
   int i;
   Atom x = XInternAtom( DISP, format, false);
   Atom *f;

   for ( i = 0; i < guts. clipboard_formats_count; i++) {
      if ( x == CF_NAME(i)) 
         return i;
   }

   if ( !( f = realloc( guts. clipboard_formats, 
      sizeof( Atom) * 3 * ( guts. clipboard_formats_count + 1)))) 
      return false;
   
   guts. clipboard_formats = f;
   CF_ASSIGN( guts. clipboard_formats_count, x, x, 8); 
   guts. clipboard_formats_count++;

   if ( hash_first_that( guts. clipboards, (void*)expand_clipboards, nil, nil, nil))
      return -1;

   return guts. clipboard_formats_count - 1;
}

Bool
apc_clipboard_deregister_format( Handle self, Handle id)
{
   return true;
}

ApiHandle
apc_clipboard_get_handle( Handle self)
{
  return C(self)-> selection;
}

static Bool
delete_xfers( Handle self, int keyLen, void * key, XWindow * window)
{
   DEFCC;
   if ( XX-> xfers) {
      int i;
      for ( i = 0; i < XX-> xfers-> count; i++) 
         delete_xfer( XX, ( ClipboardXfer*) XX-> xfers-> items[i]);     
   }
   hash_delete( guts. clipboard_xfers, window, sizeof( XWindow), false);
   return false; 
}

void
prima_handle_selection_event( XEvent *ev, XWindow win, Handle self)
{
   XCHECKPOINT;
   switch ( ev-> type) {
   case SelectionRequest: {
      XEvent xe;
      int i, id = -1;
      Atom prop   = ev-> xselectionrequest. property,
           target = ev-> xselectionrequest. target;
      self = ( Handle) hash_fetch( guts. clipboards, &ev-> xselectionrequest. selection, sizeof( Atom)); 

      guts. last_time = ev-> xselectionrequest. time;
      xe. type      = SelectionNotify;
      xe. xselection. send_event = true;
      xe. xselection. serial    = ev-> xselectionrequest. serial;
      xe. xselection. display   = ev-> xselectionrequest. display;
      xe. xselection. requestor = ev-> xselectionrequest. requestor;
      xe. xselection. selection = ev-> xselectionrequest. selection;
      xe. xselection. target    = target;
      xe. xselection. property  = None;
      xe. xselection. time      = ev-> xselectionrequest. time;
      
      Cdebug("from %08x %s at %s\n", ev-> xselectionrequest. requestor, 
             XGetAtomName( DISP, ev-> xselectionrequest. target),
             XGetAtomName( DISP, ev-> xselectionrequest. property)
             );

      if ( self) { 
         PClipboardSysData CC = C(self);
         Bool event = CC-> inside_event;
         int format, utf8_mime = 0;

         for ( i = 0; i < guts. clipboard_formats_count; i++) {
            if ( xe. xselection. target == CC-> internal[i]. name) {
               id = i;
               break;
            } else if ( i == cfUTF8 && xe. xselection. target == UTF8_MIME) {
               id = i;
               utf8_mime = 1;
               break;
            }
         }
         if ( id < 0) goto SEND_EMPTY;
         for ( i = 0; i < guts. clipboard_formats_count; i++)
            clipboard_kill_item( CC-> external, i);
         
         CC-> target = xe. xselection. target;
         CC-> need_write = false;
         
         CC-> inside_event = true;
         /* XXX cmSelection */
         CC-> inside_event = event;

         format = CF_FORMAT(id);
         target = CF_TYPE( id);
         if ( utf8_mime) target = UTF8_MIME;

         if ( id == cfTargets) { 
            int count = 0, have_utf8 = 0;
            Atom * ci;
            for ( i = 0; i < guts. clipboard_formats_count; i++) {
               if ( i != cfTargets && CC-> internal[i]. size > 0) {
                  count++;
		  if ( i == cfUTF8) {
		     count++;
		     have_utf8 = 1;
		  }
	       }
	    }
            detach_xfers( CC, cfTargets, true);
            clipboard_kill_item( CC-> internal, cfTargets);
            if (( CC-> internal[cfTargets]. data = malloc( count * sizeof( Atom)))) {
               CC-> internal[cfTargets]. size = count * sizeof( Atom);
               ci = (Atom*)CC-> internal[cfTargets]. data;
               for ( i = 0; i < guts. clipboard_formats_count; i++) 
                  if ( i != cfTargets && CC-> internal[i]. size > 0) 
                     *(ci++) = CF_NAME(i);
               if ( have_utf8) 
		  *(ci++) = UTF8_MIME;
            }
         }
        
         if ( CC-> internal[id]. size > 0) {
            Atom incr;
            int mode = PropModeReplace;
            unsigned char * data = CC-> internal[id]. data;
            unsigned long size = CC-> internal[id]. size * 8 / format;
            if ( CC-> internal[id]. size > guts. limits. request_length - 4) {
               int ok = 0;
               int reqlen = guts. limits. request_length - 4;
               /* INCR */
               if ( !guts. clipboard_xfers)
                  guts. clipboard_xfers = hash_create();
               if ( !CC-> xfers) 
                  CC-> xfers = plist_create( 1, 1);
               if ( CC-> xfers && guts. clipboard_xfers) {
                  ClipboardXfer * x = malloc( sizeof( ClipboardXfer));
                  if ( x) {
                     IV refcnt;
                     ClipboardXferKey key;
                     
                     bzero( x, sizeof( ClipboardXfer));
                     list_add( CC-> xfers, ( Handle) x);
                     x-> size = CC-> internal[id]. size;
                     x-> data = CC-> internal[id]. data;
                     x-> blocks = ( x-> size / reqlen ) + ( x-> size % reqlen) ? 1 : 0;
                     x-> requestor = xe. xselection. requestor;
                     x-> property  = prop;
                     x-> target    = xe. xselection. target;
                     x-> self      = self;
                     x-> format    = format;
                     x-> id        = id;
                     gettimeofday( &x-> time, nil);

                     CLIPBOARD_XFER_KEY( key, x-> requestor, x-> property);
                     hash_store( guts. clipboard_xfers, key, sizeof(key), (void*) x);
                     refcnt = PTR2IV( hash_fetch( guts. clipboard_xfers, &x-> requestor, sizeof( XWindow)));
                     if ( refcnt++ == 0)
                        XSelectInput( DISP, x-> requestor, PropertyChangeMask|StructureNotifyMask); 
                     hash_store( guts. clipboard_xfers, &x-> requestor, sizeof(XWindow), INT2PTR( void*, refcnt));

                     format = CF_32;
                     size = 1;
                     incr = ( Atom) CC-> internal[id]. size;
                     data = ( unsigned char*) &incr; 
                     ok = 1;
                     target = XA_INCR;
                     Cdebug("clpboard: init INCR for %08x %d\n", x-> requestor, x-> property);
                  }
               }
               if ( !ok) size = reqlen;
            }

            if ( format == CF_32) format = 32;
            XChangeProperty( 
               xe. xselection. display,
               xe. xselection. requestor,
               prop, target, format, mode, data, size);
            Cdebug("clipboard: store prop %s\n", XGetAtomName( DISP, prop));
            xe. xselection. property = prop;
         }

         /* content of PIXMAP or BITMAP is seemingly gets invalidated
            after the selection transfer, unlike the string data format */
         if ( id == cfBitmap) {
            bzero( CC-> internal[id].data, CC-> internal[id].size);
            bzero( CC-> external[id].data, CC-> external[id].size);
            clipboard_kill_item( CC-> internal, id);
            clipboard_kill_item( CC-> external, id);
         }
      }
SEND_EMPTY:
      XSendEvent( xe.xselection.display, xe.xselection.requestor, false, 0, &xe);
      XFlush( DISP);
      Cdebug("clipboard:id %d, SelectionNotify to %08x , %s %s\n", id, xe.xselection.requestor, 
         XGetAtomName( DISP, xe. xselection. property),
         XGetAtomName( DISP, xe. xselection. target)); 
   } break;
   case SelectionClear: 
      guts. last_time = ev-> xselectionclear. time;
      if ( XGetSelectionOwner( DISP, ev-> xselectionclear. selection) != WIN) {
         Handle c = ( Handle) hash_fetch( guts. clipboards, 
                                          &ev-> xselectionclear. selection, sizeof( Atom)); 
         guts. last_time = ev-> xselectionclear. time;
         if (c) {
            int i;
            C(c)-> selection_owner = nilHandle;  
            for ( i = 0; i < guts. clipboard_formats_count; i++) {
               detach_xfers( C(c), i, true);
               clipboard_kill_item( C(c)-> external, i);
               clipboard_kill_item( C(c)-> internal, i);
            }
         }
      }   
      break;
   case PropertyNotify:
      if ( ev-> xproperty. state == PropertyDelete) {
         unsigned long offs, size, reqlen = guts. limits. request_length - 4, format;
         ClipboardXfer * x = ( ClipboardXfer *) self;
         PClipboardSysData CC = C(x-> self);
         offs = x-> offset * reqlen;
         if ( offs >= x-> size) { /* clear termination */
            size = 0; 
            offs = 0;
         } else {
            size = x-> size - offs;
            if ( size > reqlen) size = reqlen;
         }
         Cdebug("clipboard: put %d %d in %08x %d\n", x-> offset, size, x-> requestor, x-> property); 
         if ( x-> format > 8)  size /= 2;
         if ( x-> format > 16) size /= 2;
	 format = ( x-> format == CF_32) ? 32 : x-> format;
         XChangeProperty( DISP, x-> requestor, x-> property, x-> target,
            format, PropModeReplace, 
            x-> data + offs, size);
         XFlush( DISP);
         x-> offset++;
         if ( size == 0) delete_xfer( CC, x);
      }
      break;
   case DestroyNotify:
      Cdebug("clipboard: destroy xfers at %08x\n", ev-> xdestroywindow. window);
      hash_first_that( guts. clipboards, (void*)delete_xfers, (void*) &ev-> xdestroywindow. window, nil, nil);
      XFlush( DISP);
      break;
   }
   XCHECKPOINT;
}

