#include "coxplot.h"

/*****************************************************************************
  This software is copyrighted and owned by the Medical College of Wisconsin.
  See the file README.Copyright for details.
******************************************************************************/

/*--------------------------------------------------------------------------
  Routines to render a memplot into an X11 window.
----------------------------------------------------------------------------*/

static Display      * old_dpy = NULL ;
static X11_colordef * old_cd  = NULL ;
static Window         old_w   = (Window) 0 ;
static GC             old_GC ;

/*--------------------------------------------------------------------------
  If we have a new X11 display, will get its coloring scheme.
  Note the tacit assumption that all windows on the same
  display in the same program will use the same visual and colormap!
----------------------------------------------------------------------------*/

static void setup_X11_plotting( Display * dpy , Window w )
{
   XGCValues gcv;

   if( old_dpy == dpy ){ old_w = w ; return ; }

   FREE_X11_colordef(old_cd) ;
   old_cd = get_X11_colordef( dpy , w ) ;

   if( old_dpy != NULL ) XFreeGC( old_dpy , old_GC ) ;
   gcv.function   = GXcopy ;
   gcv.fill_style = FillSolid ; /* 21 Mar 2001 */
   old_GC         = XCreateGC( dpy , w , GCFunction|GCFillStyle , &gcv ) ;

   old_dpy = dpy ; old_w = getwin_from_XDBE(dpy,w) ;
   return ;
}

/*---------------------------------------------------------------------------
  Set the background color of a window.  This is necessary since
  neither memplot or plotpak contains the concept of a background color,
  only the concept of the line drawing color.
-----------------------------------------------------------------------------*/

void set_X11_background( Display * dpy , Window w ,
                         unsigned char rr , unsigned char gg , unsigned char bb )
{
   unsigned long pix ;

   if( dpy == NULL || w == (Window) 0 ) return ;

   setup_X11_plotting( dpy , w ) ;
   pix = rgb_to_pixel( rr,gg,bb , old_cd ) ;
   XSetWindowBackground( dpy , getwin_from_XDBE(dpy,w) , pix ) ;
   return ;
}

/*--------------------------------------------------------------------------
   Set a sub-box within a window into which the next X11 plot should
   be scaled.  (0,0,0,0) args means use the whole window.  After
   each drawing (memplot_to_X11_sef), will be reset to the whole window
   anyway. -- 26 Feb 2001 -- RWCox
----------------------------------------------------------------------------*/

static int box_xbot=0 , box_xtop=0 ,
           box_ybot=0 , box_ytop=0  ;

void set_memplot_X11_box( int xbot, int ybot, int xtop, int ytop )
{
   if( xbot < xtop && ybot < ytop ){
      box_xbot = xbot ; box_ybot = ybot ;
      box_xtop = xtop ; box_ytop = ytop ;
   } else {
      box_xbot = box_ybot = box_xtop = box_ytop = 0 ;
   }
}

/*------------------------------------------------------------------------*/

static int rectfill = 0 ;
void set_memplot_X11_rectfill( int i ){ rectfill = i ; }  /* 26 Oct 2005 */

/*------------------------------------------------------------------------*/
/*! Get the layout of a window/pixmap.  [12 Mar 2002]
--------------------------------------------------------------------------*/

static void drawable_geom( Display *dpy , Drawable ddd ,
                           int *width , int *height , int *depth )
{
   int xx,yy ;
   unsigned int ww,hh,bb,dd ;
   Window rr ;

   XGetGeometry( dpy,ddd , &rr,&xx,&yy,&ww,&hh,&bb,&dd ) ;

   if( width  != NULL ) *width  = ww ;
   if( height != NULL ) *height = hh ;
   if( depth  != NULL ) *depth  = dd ;
}

/*--------------------------------------------------------------------------
  Actually do the rendering.
  Plotting will start with line #start and go to #end-1.
  If end <= start, will do from #start to the last one in the plot.
  To do all lines, set start=end=0.
  The mask argument controls operations, and is the bitwise sum of
  these possibilities:
    MEMPLOT_FREE_ASPECT  = allow the aspect ratio to be free
    MEMPLOT_ERASE        = draw white over the lines
----------------------------------------------------------------------------*/

#ifdef LMAX
#undef LMAX
#endif
#define LMAX 1023  /* max number of line segments to draw at once */

static XSegment xseg[LMAX] ;  /* line segment drawing buffer */
static int      nseg = 0 ;    /* number in the buffer */

static void draw_xseg(void) ; /* prototype for function below */

void memplot_to_X11_sef( Display * dpy , Window w , MEM_plotdata * mp ,
                         int start , int end , int mask                )
{
   int ii , nline , same ;
   float old_thick , old_color , new_color , new_thick ;
   float scal,xscal,yscal , xoff,yoff ;
   short x1,y1 , x2,y2 ;  /* X11 screen coords are shorts */
   int skip ;
   int w_width, w_height, w_depth ;  /* 12 Mar 2002 */
   XGCValues gcv ;

   int freee = (mask & MEMPLOT_FREE_ASPECT) != 0 ;  /* 16 Nov 2001 */
   int erase = (mask & MEMPLOT_ERASE      ) != 0 ;

   /*--- check for madness ---*/

   if( dpy == NULL || w == (Window) 0 || mp == NULL ) return ;
   if( start < 0 ) start = 0 ;

   nline = MEMPLOT_NLINE(mp) ;
   if( nline < 1 || start >= nline ) return ;

   if( end <= start || end > nline ) end = nline ;

   /*-- if we have a new X11 Display, get its coloring
        (note the tacit assumption that all windows on the same
         display in the same program will use the same visual and colormap!) --*/

   setup_X11_plotting( dpy , w ) ;

   /*-- 12 Mar 2002: replace use of XGetWindowAttributes with XGetGeometry --*/

   drawable_geom( dpy, getwin_from_XDBE(dpy,w), &w_width,&w_height,&w_depth ) ;

   if( w_depth != old_cd->depth ) return ;  /* this is bad */

   /*--- compute scaling from memplot objective
         coordinates to X11 window coordinates, maintaining aspect ---*/

   if( box_xbot >= box_xtop || box_ybot >= box_ytop ){

      xscal = (w_width -0.001) / mp->aspect ; /* aspect = x-axis objective size */
      yscal = (w_height-0.001) / 1.0 ;        /* 1.0    = y-axis objective size */
      xoff  = yoff = 0.0 ;

   } else {  /* 26 Feb 2001: scale to a given sub-box in the window */

      xscal = box_xtop - box_xbot ;
      yscal = box_ytop - box_ybot ;
      xoff  = box_xbot + 0.0      ;
      yoff  = box_ybot + 0.0      ;
   }

   if( !freee ){                           /* no aspect freedom ==> */
      if( yscal < xscal ) xscal = yscal ;  /* use smaller scaling   */
      else                yscal = xscal ;
   }
   scal = sqrt(fabs(xscal*yscal)) ;

   old_color = -1.0 ;            /* these don't occur naturally */
   old_thick = -THCODE_INVALID ;

   if( erase ){                  /* 16 Nov 2001: erase to white */
      float rr=1.0 , gg=1.0 , bb=1.0 ;
      unsigned long pix ;
      pix = rgb_to_pixel( ZO_TO_TFS(rr), ZO_TO_TFS(gg), ZO_TO_TFS(bb), old_cd ) ;
      XSetForeground( old_dpy , old_GC , pix ) ;
   }

   /* 23 Feb 2003: initialize line width to 0 for each entry
                   (in case a special case [box,circle, ...] comes first */

   gcv.line_width = 0 ;
   gcv.join_style = JoinBevel ;
   XChangeGC( old_dpy , old_GC , GCLineWidth | GCJoinStyle , &gcv ) ;

   /*--- loop over lines, scale and plot ---*/

   for( ii=start ; ii < end ; ii++ ){

      skip = 0 ;

      /* check if need to change color or thickness of line */

      new_color = MEMPLOT_COL(mp,ii) ;
      if( !erase && new_color != old_color ){
         float rr=COL_TO_RRR(new_color) ,
               gg=COL_TO_GGG(new_color) , bb=COL_TO_BBB(new_color) ;
         unsigned long pix ;

#if 0
fprintf(stderr,"Changing color to %f %f %f\n",rr,gg,bb) ;
#endif

         draw_xseg() ; /* must draw before changing GC */

         pix = rgb_to_pixel( ZO_TO_TFS(rr), ZO_TO_TFS(gg), ZO_TO_TFS(bb), old_cd ) ;
         XSetForeground( old_dpy , old_GC , pix ) ;
         old_color = new_color ;
      }

      new_thick = MEMPLOT_TH(mp,ii) ;
      if( new_thick < 0.0 ){               /* 21 Mar 2001: negative thickness codes */
         int thc = (int)(-new_thick) ;
         switch( thc ){  /* default is to do nothing (e.g., thd = THCODE_INVALID) */

            case THCODE_RECT:{        /* rectangle */
               short xb,yb , xt,yt ;
               unsigned short w,h ;
               x1 = (short)( xoff + xscal * MEMPLOT_X1(mp,ii)         ) ;
               x2 = (short)( xoff + xscal * MEMPLOT_X2(mp,ii)         ) ;
               y1 = (short)( yoff + yscal * (1.0 - MEMPLOT_Y1(mp,ii)) ) ;
               y2 = (short)( yoff + yscal * (1.0 - MEMPLOT_Y2(mp,ii)) ) ;
               if( x1 < x2 ){ xb=x1; xt=x2; } else { xb=x2; xt=x1; }
               if( y1 < y2 ){ yb=y1; yt=y2; } else { yb=y2; yt=y1; }
               w = xt-xb ; h = yt-yb ;
               if( w || h ){
                 if( rectfill )
                   XFillRectangle( old_dpy,old_w,old_GC , xb,yb,w,h ) ;
                 else
                   XDrawRectangle( old_dpy,old_w,old_GC , xb,yb,w,h ) ;
               } else
                 XDrawPoint( old_dpy,old_w,old_GC , xb,yb ) ;
               skip = 1 ;
            }
            break ;

            case THCODE_CIRC:{        /* circle */
               int xcor,ycor , xcen,ycen , xrad,yrad ;
               unsigned int ww, hh ;
               xcen = (int)(xoff + xscal * MEMPLOT_X1(mp,ii)         );
               ycen = (int)(yoff + yscal * (1.0 - MEMPLOT_Y1(mp,ii)) );
               xrad = (int)(       xscal * MEMPLOT_X2(mp,ii)         );
               yrad = (int)(       yscal * MEMPLOT_X2(mp,ii)         );
               xcor = xcen - xrad ; ww = 2*xrad ;
               ycor = ycen - yrad ; hh = 2*yrad ;
               if( ww || hh )
                 XDrawArc( old_dpy,old_w,old_GC , xcor,ycor,ww,hh , 0,360*64 ) ;
               else
                 XDrawPoint( old_dpy,old_w,old_GC , xcor,ycor ) ;
               skip = 1 ;
            }
            break ;
         }

      } else if( new_thick != old_thick ){ /* normal case: change line thickness */
         int lw = scal * new_thick ;
         if( lw < 0 ) lw = 0 ;
#if 0
fprintf(stderr,"Changing thickness: old=%f  new=%f\n",old_thick,new_thick) ;
#endif

         draw_xseg() ; /* must draw before changing GC */

         gcv.line_width = lw ;
         gcv.join_style = JoinBevel ;
         XChangeGC( old_dpy , old_GC , GCLineWidth | GCJoinStyle , &gcv ) ;
         old_thick = new_thick ;
      }

      if( nseg == LMAX ) draw_xseg() ;  /* draw if list is full */

      /* scale coords to X11 shorts (also see zzphph.f) */
      /* 26 Feb 2001: xoff,yoff are now variables, instead of 0.499 */

      if( !skip ){
        x1 = (short)( xoff + xscal * MEMPLOT_X1(mp,ii)         ) ;
        x2 = (short)( xoff + xscal * MEMPLOT_X2(mp,ii)         ) ;
        y1 = (short)( yoff + yscal * (1.0 - MEMPLOT_Y1(mp,ii)) ) ;
        y2 = (short)( yoff + yscal * (1.0 - MEMPLOT_Y2(mp,ii)) ) ;

      /* add to segment list */

        xseg[nseg].x1 = x1 ; xseg[nseg].y1 = y1 ;
        xseg[nseg].x2 = x2 ; xseg[nseg].y2 = y2 ; nseg++ ;
      }
   }

   /*-- process any segments left over --*/

   draw_xseg() ;
   set_memplot_X11_box(0,0,0,0) ; /* 26 Feb 2001: clear box */
   return ;
}

/*---------------- draw the line segments stored in xseg -----------------*/

static void draw_xseg(void)
{
   int jbot,jtop , ii,nj ;
   XPoint xpt[LMAX+1] ;

   if( nseg <= 0 ) return ;  /* nothing to do */

#if 0
fprintf(stderr,"draw_xseg: %d segments input.\n",nseg) ;
for( ii=0 ; ii < nseg ; ii++ )
  fprintf(stderr,"  %4d: x1=%4d  y1=%4d   x2=%4d  y2=%4d\n",
          ii , xseg[ii].x1 , xseg[ii].y1 , xseg[ii].x2 , xseg[ii].y2 ) ;
#endif

   jbot = 0 ;
   while( jbot < nseg ){  /* scan the line segment list starting at jbot */

      /* scan forward to find a set of connected lines */

      jtop = jbot+1 ;
      while( jtop < nseg ){    /* 23 Feb 2003: more complex connection tests */

        if(    xseg[jtop-1].x2 == xseg[jtop].x1
            && xseg[jtop-1].y2 == xseg[jtop].y1 ){ jtop++; continue; } /* OK */

        if(    xseg[jtop-1].x2 == xseg[jtop].x2
            && xseg[jtop-1].y2 == xseg[jtop].y2 ){          /* OK if flipped */

          ii = xseg[jtop].x2; xseg[jtop].x2 = xseg[jtop].x1; xseg[jtop].x1 = ii;
          ii = xseg[jtop].y2; xseg[jtop].y2 = xseg[jtop].y1; xseg[jtop].y1 = ii;
          jtop++; continue;
        }

        break ;    /* get to here ==> jtop-1 and jtop segments not connected */
      }

      /* jbot .. jtop-1 are connected;
         if this is more than one line, draw them together
         so that the X11 server will properly join the lines */

      nj = jtop - jbot ;
      if( nj > 1 ){
         xpt[0].x = xseg[jbot].x1 ; xpt[0].y = xseg[jbot].y1 ;
         for( ii=0 ; ii < nj ; ii++ ){
            xpt[ii+1].x = xseg[jbot+ii].x2 ; xpt[ii+1].y = xseg[jbot+ii].y2 ;
         }
         XDrawLines( old_dpy,old_w,old_GC , xpt,nj+1 , CoordModeOrigin ) ;

#if 0
fprintf(stderr,"draw_xseg: XDrawLines for %d\n",nj) ;
#endif

         jbot = jtop ; continue ;  /* start the while loop over */
      }

      /* jbot is not connected to jbot+1;
         scan forward to find a set of disconnected lines */

      while( jtop < nseg                            &&
             ( xseg[jtop-1].x2 != xseg[jtop].x1 ||
               xseg[jtop-1].y2 != xseg[jtop].y1   ) &&
             ( xseg[jtop-1].x2 != xseg[jtop].x2 ||
               xseg[jtop-1].y2 != xseg[jtop].y2   )    ) jtop++ ;

      /* jbot .. jtop-1 are disconnected, so draw them as such */

      XDrawSegments( old_dpy,old_w,old_GC , xseg+jbot , jtop-jbot ) ;

#if 0
fprintf(stderr,"draw_xseg: XDrawSegments for %d\n",jtop-jbot) ;
#endif

      jbot = jtop ; continue ;  /* start the while loop over */
   }

   nseg = 0 ; return ;
}

/*------------------------------------------------------------------------
  Returns position of highest set bit in 'ul' as an integer (0-31),
  or returns -1 if no bit is set.
--------------------------------------------------------------------------*/

static int highbit(unsigned long ul)
{
  int i;  unsigned long hb;

  hb = 0x80;  hb = hb << 24;   /* hb = 0x80000000UL */
  for (i=31; ((ul & hb) == 0) && i>=0;  i--, ul<<=1);
  return i;
}

/*-------------------------------------------------------------------------
  Return an X11 pixel value that represents the given color.
  Inputs rr,gg,bb are from 0 to 255.  Output is an unsigned long,
  the standard X11 format for specifying colors to a GC.
  Note that if the result is to be put into an XImage, further
  bit manipulation must be done on the result.
---------------------------------------------------------------------------*/

unsigned long rgb_to_pixel( unsigned char rr , unsigned char gg ,
                            unsigned char bb , X11_colordef * cd )
{
   /*--- TrueColor case: make color by appropriate bit twiddling ---*/

   if( cd->classKRH == TrueColor ){
      unsigned long r , g , b , rgb ;

      r = (cd->rrshift<0) ? (rr<<(-cd->rrshift))
                          : (rr>>cd->rrshift)   ; r = r & cd->rrmask ;

      g = (cd->ggshift<0) ? (gg<<(-cd->ggshift))
                          : (gg>>cd->ggshift)   ; g = g & cd->ggmask ;

      b = (cd->bbshift<0) ? (bb<<(-cd->bbshift))
                          : (bb>>cd->bbshift)   ; b = b & cd->bbmask ;

      rgb = r | g | b ;  /* assemble color from components */
      return rgb ;
   }

   /*--- PseudoColor case: find closest match in colormap.
         Red, green, and blue are weighted according
         to their importance to the human visual system. ---*/

#define RW 2  /* the weights alluded to above */
#define GW 4
#define BW 1

   if( cd->classKRH == PseudoColor ){
      int ii , rdif,gdif,bdif,dif , ibest,dbest ;

      rdif = cd->rr[0] - rr ;
      gdif = cd->gg[0] - gg ;
      bdif = cd->bb[0] - bb ; dif = RW*abs(rdif)+GW*abs(gdif)+BW*abs(bdif) ;
      if( dif == 0 ) return 0 ;

      ibest = 0 ; dbest = dif ;
      for( ii=1 ; ii < cd->ncolors ; ii++ ){
         rdif = cd->rr[ii] - rr ;
         gdif = cd->gg[ii] - gg ;
         bdif = cd->bb[ii] - bb ; dif = RW*abs(rdif)+GW*abs(gdif)+BW*abs(bdif) ;
         if( dif == 0 ) return ii ;
         if( dif < dbest ){ ibest = ii ; dbest = dif ; }
      }
      return ibest ;
   }

   /*--- Illegal case! ---*/

   return 0 ;  /* always valid */
}

/*-------------------------------------------------------------------------*/

static volatile int xwasbad ;  /* 13 Mar 2002 */

typedef int (*xhandler)(Display *, XErrorEvent *) ;

static int qhandler( Display *dpy , XErrorEvent *xev )
{
   xwasbad = 1 ; return 0 ;
}

/*-------------------------------------------------------------------------
   Return a structure that defines the colors available for the given
   window.  This only works for PseudoColor and TrueColor visuals.
   Returns NULL if an error occurs.
---------------------------------------------------------------------------*/

X11_colordef * get_X11_colordef( Display * display , Window w )
{
   Status sss ;
   XWindowAttributes xwat ;
   XColor * xcol ;
   XVisualInfo vinfo , * vin ;
   X11_colordef * cd ;          /* will be the output */
   int count , ii ;
   xhandler old_handler ;       /* 13 Mar 2002 */

   /*--- sanity check ---*/

   if( display == NULL || w == (Window) 0 ) return NULL ;

   /*--- get window attributes ---*/

   /* 13 Mar 2002: deal with the error that occurs
                   when the Window is really a Pixmap */

   xwat.depth = 0 ;
   old_handler = XSetErrorHandler(qhandler) ; xwasbad = 0 ;

   XGetWindowAttributes( display, getwin_from_XDBE(display,w), &xwat ) ;

   (void) XSetErrorHandler(old_handler) ;

   if( xwasbad ){
      int xx,yy ; unsigned int ww,hh,bb,dd ; Window rr ;
      XGetGeometry( display,w , &rr,&xx,&yy,&ww,&hh,&bb,&dd ) ;
      XGetWindowAttributes( display, rr , &xwat ) ;
   }
   if( xwat.depth == 0 ) return NULL ;   /* bad news */

   /*--- get information about the window's Visual ---*/

   vinfo.visualid = XVisualIDFromVisual(xwat.visual) ;
   vin = XGetVisualInfo( display , VisualIDMask , &vinfo , &count ) ;
   if( count == 0 || vin == NULL ) return NULL ;

   /*--- PseudoColor case ---*/
#if defined(__cplusplus) || defined(c_plusplus)
   if( vin->c_class == PseudoColor ){
#else
   if( vin->class == PseudoColor ){
#endif
      int iz ;

      /* create output */

      cd = (X11_colordef *) malloc( sizeof(X11_colordef) ) ;
      cd->classKRH = PseudoColor ;
      cd->depth = vin->depth ;

      /* get all the colors in the colormap */

      count = vin->colormap_size ;
      xcol  = (XColor *) malloc( sizeof(XColor) * count ) ;
      for( ii=0 ; ii < count ; ii++ ) xcol[ii].pixel = ii ;

      XQueryColors( display , xwat.colormap , xcol , count ) ;

      /* store them in the output, truncated to 8 bits */

      cd->ncolors = count ;
      cd->rr      = (unsigned char *) malloc( count ) ;
      cd->gg      = (unsigned char *) malloc( count ) ;
      cd->bb      = (unsigned char *) malloc( count ) ;

      for( ii=0 ; ii < count ; ii++ ){
         cd->rr[ii] = xcol[ii].red   >> 8 ;
         cd->gg[ii] = xcol[ii].green >> 8 ;
         cd->bb[ii] = xcol[ii].blue  >> 8 ;
      }

      /* find first all zero color; discard others at end of colormap */

      for( iz=0 ; iz < count ; iz++ )
         if( cd->rr[iz] == 0 && cd->gg[iz] == 0 && cd->bb[iz] == 0 ) break ;

      if( iz < count-1 ){  /* if found one before the end */

         for( ii=count-1 ; ii > iz ; ii-- )  /* scan backwards */
            if( cd->rr[ii] != 0 || cd->gg[ii] != 0 || cd->bb[ii] != 0 ) break ;

         count = ii+1 ;  /* number of colors left */

         if( count == 1 ){ /* colormap is all black?! */
            free(xcol) ; XFree(vin) ; FREE_X11_colordef(cd) ; return NULL ;
         }

         cd->ncolors = count ;
      }

      free(xcol) ; XFree(vin) ; return cd ;
   }

   /*--- TrueColor case ---*/
#if defined(__cplusplus) || defined(c_plusplus)
   if( vin->c_class == TrueColor ){
#else
   if( vin->class == TrueColor ){
#endif

      /* create output */

      cd = (X11_colordef *) malloc( sizeof(X11_colordef) ) ;
      cd->classKRH = TrueColor ;
      cd->depth = vin->depth ;

      cd->rrmask  = vin->red_mask ;            /* bit masks for color  */
      cd->ggmask  = vin->green_mask ;          /* storage inside pixel */
      cd->bbmask  = vin->blue_mask ;
      cd->rrshift = 7 - highbit(cd->rrmask) ;  /* shift puts high bit of  */
      cd->ggshift = 7 - highbit(cd->ggmask) ;  /* a color byte into place */
      cd->bbshift = 7 - highbit(cd->bbmask) ;  /* +shift == >> ; - == <<  */

      cd->rr = cd->gg = cd->bb = NULL ;        /* not used */

      XFree(vin) ; return cd ;
   }

   /*--- Illegal Visual class! ---*/

   XFree(vin) ; return NULL ;
}

#ifdef HAVE_XDBE
/*----------------------------------  24 Jan 1999  -------------------------------*/
void init_XDBE( Display * dpy )
{
   int sss , ii , jj ;
   char * ec ;

   if( use_xdbe >= 0 ) return ;

   ec = getenv("AFNI_NO_XDBE") ;  /* 28 Jan 2000 - add this environment variable */
   if( ec != NULL && (ec[0]=='Y' || ec[0]=='y') ){
      use_xdbe = 0 ;
   } else {
      sss = (int) XdbeQueryExtension( dpy , &ii , &jj ) ;
      use_xdbe = (sss != 0 ) ;
   }
   return ;
}

Window getwin_from_XDBE( Display * dpy , Drawable w )
{
   XdbeBackBufferAttributes * bat ;
   Window bw ;

   if( w == (Window) 0 || use_xdbe <= 0 ) return w ;

   bat = XdbeGetBackBufferAttributes( dpy , w ) ;
   bw  = bat->window ; XFree(bat) ;
   if( bw == (Window) 0 ) bw = w ;
   return bw ;
}
#endif  /* HAVE_XDBE */


syntax highlighted by Code2HTML, v. 0.9.1