/** Driver for using GTK+ drawing functions  
 *  Transcripted from Motif to GTK+ by P. Vincent to
 *  Replace   x11drv.c
 */
#include <gtk/gtk.h>

#include "gg_gtkdrv.h"

#include "globals.h"
#include "utils.h"
#include "device.h"
#include "draw.h"
#include "graphs.h"
#include "patterns.h"

#include "gg_protos.h"

GdkColor  bleuciel ,wheat;

int install_cmap = CMAP_INSTALL_AUTO;

void gg_color_pannel_update (void);

extern GtkWidget *gg_main_window;
extern GtkWidget *gg_canvas;

extern GdkDisplay *gg_disp;

GdkWindow *gg_root;

extern void 	 drawgraph(void);
extern GdkPixmap *gg_resize_bufpixmap (int w,  int h);

extern GdkWindow *gg_win;  /* replace xwin */

extern GdkScreen *gg_screen;   // screen

static GdkVisual *gg_visual;
static GdkColormap *gg_cmap = NULL;
GdkGC *gg_gc ,*gg_bg_gc ,*gg_gcxor;   // gc , gcxor
static GdkGC *gg_default_gc;
static GdkPixmap *gg_displaybuff = NULL ;  // displaybuff for Motif

GdkColor gg_colors[MAXCOLORS];  /* gdk functions arg are GdkColor, not ->pixel  */
static int gg_color_fg;
static int gg_color_bg;
static int gg_color_last = 0;
static int gg_patno;
static int gg_linewidth;
static int gg_linestyle;
static int gg_fillrule;
static int gg_arcfillmode;
static int gg_linecap;
static int gg_linejoin;

static  gint depth;

gint gg_win_h = 0, gg_win_w = 0;  // win_h
#define gg_win_scale ((gg_win_h < gg_win_w) ? gg_win_h:gg_win_w)

static double pg_width_ori ,pg_height_ori;


/** Declare and initialize the Device_entry for the GTK driver
 *  Replace dev_x11
 *  See device.h 80
 */
static Device_entry dev_gtk = {DEVICE_TERM,     	    /* type */
          "GTK",			        	    /* *name : name of device */                            
          gg_initgraphics,		        	    /* function to initialize device */             
          NULL,                                 	    /* function to parse device-specific commands */
          NULL,						    /* function (GUI interface) to setup device */  
          "",						    /* filename extension */                        
          FALSE,					    /* device has its own fonts */                  
          TRUE,						    /* font antialiasing */                         
          {DEFAULT_PAGE_WIDTH, DEFAULT_PAGE_HEIGHT, 72.0},  /* device defaults */                           
          NULL						    /* device private data */                       
         };


/***********************************************/

/** Register the Device_entry structure
 *  Replace register_x11_drv
 */
int register_gtk_drv(void)
{
  int max_path_limit = MAX_DRAWING_PATH;

  set_max_path_limit(max_path_limit);

  dev_gtk.pg.dpi =  rint( MM_PER_INCH * gdk_screen_get_width(gg_screen) /
			 gdk_screen_get_width_mm (gg_screen) );

  return register_device(dev_gtk);
}


/********************* gg_init **************************/

/** Initialize the Graphic Context
 *  Replace xlibinit 
 */
int gg_init (void) 
{
  GdkGCValues gg_gc_val;

  gg_screen = gdk_screen_get_default ();
  /*  screennumber is not used */
  gg_visual = gdk_visual_get_system ();

  gg_root = gdk_get_default_root_window ();

  /* It seems that the DefaultGC gc is no use here
   * and that gtk_style_attach can be the best replacement
   * if needed
   * As a workaround, I initalize gg_gc in gg_expose_resize_CB
   */

  // ceci n'est pas correct: gdk_visual_get_best_depth => 32
  // alors que gdk_drawable_get_depth (gg_main_window->window)) => 24
  // mais il faut le faire après  gtk_widget_show_all (gg_main_window);
  depth = gdk_visual_get_best_depth ();
  /* I think that pixel_size is of no use with GTK */ 

  /*  Init colormap  */
  gg_cmap = gdk_colormap_new (gg_visual ,TRUE);
  gg_initcmap ();

  /* Keyboard keymap */
  // gg_keymap = gdk_keymap_get_default ();

  /* set GCs  */
  gg_gc_val.foreground = gg_colors[0];
  gg_gc_val.background = gg_colors[1];
  if (invert) {
    gg_gc_val.function = GDK_INVERT;
  } else {
    gg_gc_val.function = GDK_XOR;
  }
  gg_gcxor = gdk_gc_new_with_values (gg_root ,&gg_gc_val
				     ,GDK_GC_FUNCTION | GDK_GC_FOREGROUND);

  /* with  Motif, it is displaybuff  */
  gg_displaybuff = gg_resize_bufpixmap (gg_win_w, gg_win_h);

  /** Tile pixmap for patterns.
   *  Replace curtile  
   */
  //  gg_curtile = gdk_pixmap_new (NULL ,16 ,16 ,depth);

  /* No monomode and no AA fonts */


  return RETURN_SUCCESS;
}


/***********************************************/

/* Replace xconvxlib et yconvxlib */
int gg_xconvglib (double x) 
{
  return ((int) rint(gg_win_scale * x)); 
}
int gg_yconvglib (double y) 
{
  return ((int) rint(gg_win_h - gg_win_scale * y)); 
}

/** Convert VPoint in device coordinate
 *  Replace xlibVPoint2dev
 */
void gg_VPoint2dev (VPoint vp, int *x, int *y)
{
    *x = gg_xconvglib (vp.x);
    *y = gg_yconvglib (vp.y);
}

/** Convert VPoint in a GdkPoint
 * Replace VPoint2XPoint
 */
GdkPoint gg_VPoint2GdkPoint (VPoint vp)
{
  GdkPoint p;   // des gint

  p.x = gg_xconvglib (vp.x);
  p.y = gg_yconvglib (vp.y);

  return (p);
}

/***********************************************/

/**
 * gg_gtk2VPoint - given (x,y) in screen coordinates,
 * return the  viewport coordinates.
 * Replace xlibdev2VPoint
 */
VPoint gg_gtk2VPoint(int x, int y)
{
    VPoint vp;

    if (gg_win_scale == 0) {
        vp.x = (double) 0.0;
        vp.y = (double) 0.0;
    } else {
        vp.x = (double) x / gg_win_scale;
        vp.y = (double) (gg_win_h - y) / gg_win_scale;
    }

    return (vp);        
}

/***********************************************/

/** Used for antialiasing in t1fonts
 *  Replace xlibupdatecmap  . See devupdatecmap
 */
void gg_updatecmap(void)
{
  /*  */
  if (inwin) {
    gg_initcmap ();
    gg_color_pannel_update ();
  }
}

/***********************************************/

/** Initialize the colormap via devinitcmap
 *  Replace xlibinitcmap     x11drv.c  264
 */
void gg_initcmap (void)
{
  int i ,n;
  RGB *prgb;

  for (i = 0; i < MAXCOLORS; i++) {
    gg_colors[i].pixel = 0;
    gg_colors[i].red   = 0;
    gg_colors[i].green = 0;
    gg_colors[i].blue  = 0;
  }
    
  n = number_of_colors();
  for (i = 0; i < n; i++) {
    prgb = get_rgb(i);
    if (prgb != NULL) {
      gg_colors[i].red   = (gint16)(prgb->red  *257);
      gg_colors[i].green = (gint16)(prgb->green*257);
      gg_colors[i].blue  = (gint16)(prgb->blue *257);
      if(!gdk_colormap_alloc_color (gg_cmap ,&gg_colors[i] ,TRUE ,TRUE)) {
	printf ("  gg_initcmap: alloc_color %d failed\n" ,i);
      }
    } else {
      printf ("  gg_initcmap: prgb NULL for i=%d \n" ,i);
    }
  }
  gg_color_last = n;
}

/**
 *  Replace sync_canvas_size
 */
void gg_sync_canvas_size (gint *w, gint *h, int inv)
{
  GtkRequisition req;
  Page_geometry pg = get_page_geometry();

  if (inv) {
    gtk_widget_size_request (gg_canvas ,&req);
    *w = (unsigned int)req.width;
    *h = (unsigned int)req.height;
    /* Ce trafic me semble bizarre, mais ca marche */
    set_page_dimensions (*w*72.0/pg.dpi, *h*72.0/pg.dpi, TRUE);
  } else {
    *w = (gint) pg.width;
    *h = (gint) pg.height;
    gtk_widget_set_size_request (gg_canvas ,*w ,*h);
  }
}


/** Initialize the driver functions to draw on the pixmap
 *  Replace xlibinitgraphics  x11drv.c  309
 *  Called by drawgraph   in plotone.c   59
 */
int gg_initgraphics (void)
{
  if (inwin == FALSE) return RETURN_FAILURE;
  
  /* These values  triggers the driver's functions  */
  gg_color_fg  = BAD_COLOR;
  gg_color_bg = BAD_COLOR;
  gg_patno = -1;
  gg_linewidth = -1;
  gg_linestyle = -1;
  gg_fillrule = -1;
  gg_arcfillmode = -1;
  gg_linecap   = -1;
  gg_linejoin  = -1;
  
  /* device-dependent routines
   * Replace  x11drv.c 329 
   */     
  devupdatecmap   = gg_updatecmap;
  devdrawpixel    = gg_drawpixel;
  devdrawpolyline = gg_drawpolyline;
  devfillpolygon  = gg_fillpolygon;
  devdrawarc      = gg_drawarc;
  devfillarc      = gg_fillarc;
  devputpixmap    = gg_putpixmap;
  devleavegraphics= gg_leavegraphics;
  devsetfrgbcolor = gg_setfrgbcolor;

  /* init settings specific to GTK driver  */
  /* Implied  PAGE_FIXED */
  gg_sync_canvas_size (&gg_win_w, &gg_win_h, FALSE);
  gg_displaybuff = gg_resize_bufpixmap (gg_win_w, gg_win_h);  
  if (inwin)  gg_updatecmap();

/* Recupere le contexte graphique associe' au widget */
  gg_default_gc = gdk_gc_new (GDK_DRAWABLE (gg_canvas->window));
  gg_gc = gdk_gc_new (GDK_DRAWABLE (gg_canvas->window));
  gg_updatecmap ();     // PV:  inutile ?

  /* default to   bg=0=white   fg=1=black  */
  return RETURN_SUCCESS;
}


/***********************************************/

/**  Update the GdkGC used to draw the Drawable
 *   Replace xlib_setpen
 *   Used only in this file
 */
void gg_setpen (void)
{
  int fg, bg, p;
  GdkPixmap *ptmp;

  fg = getcolor();
  bg = getbgcolor();
  p = getpattern();
    
  if ((fg == gg_color_fg) && (bg == gg_color_bg) && (p == gg_patno))  return;
  if (fg != gg_color_fg) {
    gdk_gc_set_foreground (gg_gc ,&gg_colors[fg]);
    gg_color_fg = fg;
  }
   
  if (bg != gg_color_bg) {
    //    gdk_gc_set_background (gg_gc ,&gg_colors[bg]);
    gg_color_bg = bg;
  }

  if (p >= number_of_patterns() || p < 0)  p = 0;
  gg_patno = p;
  
  if (p == 0) { /* TODO: transparency !!!*/
    return;
  } else if (p == 1) {
    gdk_gc_set_fill (gg_gc ,GDK_SOLID);
  } else if (p == 44) {  /* hexagons */
    ptmp = gdk_bitmap_create_from_data (NULL ,(char *) pat_bits[p], 30 ,18);
    gdk_gc_set_stipple (gg_gc ,ptmp);
    g_object_unref (ptmp);
    gdk_gc_set_fill (gg_gc ,GDK_STIPPLED);
    return;
  } else {
    /* TODO: implement cache ? */
    ptmp = gdk_bitmap_create_from_data (NULL ,(char *) pat_bits[p], 16, 16);
    gdk_gc_set_stipple (gg_gc ,ptmp);
    g_object_unref (ptmp);

    gdk_gc_set_fill (gg_gc ,GDK_STIPPLED);
    //    gdk_gc_set_fill (gg_gc ,GDK_TILED);   don't work
    //    gdk_gc_set_tile (gg_gc ,gg_curtile);
    return;
  }
}

/********************* gg_setdrawbrush **************************/

/** Set the brush to draw ploylines, pixels,..
 * Replace xlib_setdrawbrush
 */
void gg_setdrawbrush (void)
{
  unsigned int iw;
    int style;
    int lc, lj;
    int i, scale, darr_len;
    gint8 *xdarr;

  gg_setpen();
    
  iw = (unsigned int) rint(getlinewidth() * gg_win_scale);
  if (iw == 1) {
    iw = 0;
  }
  style = getlinestyle();
  lc = getlinecap();
  lj = getlinejoin();
    
  switch (lc) {
  case LINECAP_BUTT:
    lc = GDK_CAP_BUTT;
    break;
  case LINECAP_ROUND:
    lc =  GDK_CAP_ROUND;
    break;
  case LINECAP_PROJ:
    lc = GDK_CAP_PROJECTING;
    break;
  }

  switch (lj) {
  case LINEJOIN_MITER:
    lj = GDK_JOIN_MITER;
    break;
  case LINEJOIN_ROUND:
    lj = GDK_JOIN_ROUND;
    break;
  case LINEJOIN_BEVEL:
    lj = GDK_JOIN_BEVEL;
    break;
  }
    
  if (iw != gg_linewidth || style != gg_linestyle ||
      lc != gg_linecap   || lj    != gg_linejoin) {
    if (style > 1) {
      darr_len = dash_array_length[style];
      xdarr = xmalloc ( darr_len * sizeof(gint8));
      if (xdarr == NULL) {
	return;
      }
      scale = MAX2(1, iw);
      for (i = 0; i < darr_len; i++) {
	xdarr[i] = scale*dash_array[style][i];
      }
      gdk_gc_set_line_attributes (gg_gc ,iw ,GDK_LINE_ON_OFF_DASH ,lc ,lj);
      gdk_gc_set_dashes (gg_gc ,0 ,xdarr ,darr_len);
      xfree(xdarr);
    } else if (style == 1) {
      gdk_gc_set_line_attributes (gg_gc ,iw ,GDK_LINE_SOLID ,lc ,lj);
    }
    gg_linestyle = style;
    gg_linewidth = iw;
    gg_linecap   = lc;
    gg_linejoin  = lj;
  }
}

/*********************** gg_drawpixel ************************/

/** Draw a VPoint (pixel)
 *  Replace xlibdrawpixel
 */
void gg_drawpixel (VPoint vp)  //
{
  GdkPoint xp;

  xp = gg_VPoint2GdkPoint(vp);
  gg_setpen ();
  gdk_draw_point (gg_displaybuff ,gg_gc ,xp.x ,xp.y);
}

/************************ gg_drawpolyline **********************/

/** Draw a polyline
 *  Replace xlibdrawpolyline   x11drv.c  502
 */
void gg_drawpolyline (VPoint *vps, int n, int mode)
{
  int i, xn = n;
  GdkPoint *p;

  if (n <= 1 || getlinestyle() == 0 || getpattern() == 0) return;
  if (mode == POLYLINE_CLOSED) xn++;
  p = xmalloc (xn * sizeof(GdkPoint));
  if (p == NULL) return;

  for (i = 0; i < n; i++) {
    p[i] = gg_VPoint2GdkPoint(vps[i]);
  }
  if (mode == POLYLINE_CLOSED) p[n] = p[0];

  gg_setdrawbrush();
  gdk_draw_lines (gg_displaybuff ,gg_gc ,p ,xn);

  xfree(p);
}

/************************* gg_fillpolygon **********************/

/** Fill a polygon
 *  Replace  xlibfillpolygon called by DrawPolygon
 *  and used to fill background in drawgraph (plotone.c 84)
 */
void gg_fillpolygon (VPoint *vps, int npoints)
{
  int i;
  GdkPoint *p;
  GdkRegion *gg_region;


  if (npoints < 3 || getpattern() == 0) return;
  p = xmalloc (npoints * sizeof(GdkPoint));
  if (p == NULL) return;
 
  for (i = 0; i < npoints; i++) {
    p[i] = gg_VPoint2GdkPoint(vps[i]);
  }

  gg_setpen();  
  
  setfillrule (FILLRULE_WINDING);

  if (getfillrule() != gg_fillrule) {
    gg_fillrule = getfillrule();
    if (getfillrule() == FILLRULE_WINDING) {
      gg_region = gdk_region_polygon (p ,npoints ,GDK_WINDING_RULE);
    } else {
      printf ("gg_fillpolygon:  GDK_EVEN_ODD_RULE  npoints=%d\n" ,npoints);
      gg_region = gdk_region_polygon (p ,npoints ,GDK_EVEN_ODD_RULE);
    }
    gdk_gc_set_clip_region (gg_gc ,gg_region);
  }

  gdk_draw_polygon (gg_displaybuff ,gg_gc ,TRUE ,p ,npoints);

  xfree(p);
}

/************************* gg_drawarc **********************/

/**
 *  Replace xlibdrawarc
 */
void gg_drawarc (VPoint vp1, VPoint vp2, int angle1, int angle2)
{
    int x1, y1, x2, y2;

    gg_VPoint2dev (vp1, &x1, &y2);
    gg_VPoint2dev (vp2, &x2, &y1);

    if (getlinestyle() == 0 || getpattern() == 0) return;

    gg_setdrawbrush();
    
    if (x1 != x2 || y1 != y2) {
      gdk_draw_arc (gg_displaybuff ,gg_gc ,FALSE
		    ,MIN2(x1, x2), MIN2(y1, y2)
		    ,abs(x2 - x1), abs(y2 - y1)
		    , 64 * angle1, 64 * (angle2 - angle1)
		    );

    } else { /* zero radius */
      gdk_draw_point  (gg_displaybuff ,gg_gc ,x1 ,y1);
    }
}

/************************* gg_fillarc **********************/

/**
 *  Replace xlibfillarc   x11drv.c  596
 */
void gg_fillarc (VPoint vp1, VPoint vp2, int angle1, int angle2, int mode)
{
  int x1, y1, x2, y2;

  gg_VPoint2dev (vp1, &x1, &y2);
  gg_VPoint2dev (vp2, &x2, &y1);

  if (getpattern() != 0) {
    gg_setpen ();
    if (x1 != x2 || y1 != y2) {
      if (gg_arcfillmode != mode) {
	gg_arcfillmode = mode;
	if (mode == ARCFILL_CHORD) {
	  //	  printf ("gg_fillarc : ARCFILL_CHORD A FAIRE \n");
	} else {
	  //	  printf ("gg_fillarc : ArcPieSlice A FAIRE \n");
	}
      }
      gdk_draw_arc (gg_displaybuff ,gg_gc ,TRUE
		    ,MIN2(x1, x2), MIN2(y1, y2)
		    ,abs(x2 - x1), abs(y2 - y1)
		    , 64 * angle1, 64 * (angle2 - angle1)
		      );
    } else { /* zero radius */
      gdk_draw_point  (gg_displaybuff ,gg_gc ,x1 ,y1);
    }
  }
}

/************************* gg_putpixmap **********************/

/** Used by t1fonts only
 *  Replace xlibputpixmap called via devputpixmap
 */

void gg_putpixmap (VPoint vp, int width, int height, 
		   char *databits, int pixmap_bpp, int bitmap_pad, int pixmap_type)
{
  GdkPoint xp;
  int j, k;
  int cindex;
  GdkImage *gg_image;
  guint32 old_pix ,pix;
  gint xx ,yy ,ww ,hh ,kk ,jj;

  xp = gg_VPoint2GdkPoint(vp);

  /* Clipping (for strings) */
  xx = (xp.x < 0) ? 0 : xp.x;
  yy = (xp.y < 0) ? 0 : xp.y;
  ww = (xp.x + width  > gg_win_w) ? gg_win_w - xx : width;
  hh = (xp.y + height > gg_win_h) ? gg_win_h - yy : height;
  int bg = getbgcolor ();
  if (pixmap_bpp == 1) {
    printf ("gg_putpixmap : pixmap_bpp==1 reste A FAIRE \n");
    return;
  }
  if (ww > 0 && hh > 0) {
    gg_image = gdk_drawable_copy_to_image (gg_displaybuff ,NULL ,xx ,yy ,0 ,0 ,ww ,hh);
    old_pix = 0;
    for (k = yy - xp.y ,kk = 0; k < height; k++) {
      if (yy+k < gg_win_h) {
	for (j = xx - xp.x ,jj = 0; j < width; j++) {
	  if (xx+j < gg_win_w) {
	    cindex = (unsigned char) (databits)[k*width+j];
	    if (cindex != bg) {
	      pix = gg_colors[cindex].pixel;
	      gdk_image_put_pixel (gg_image ,jj ,kk ,pix);
	    } else {
	      old_pix = gdk_image_get_pixel (gg_image ,jj ,kk);
	      gdk_image_put_pixel (gg_image ,jj ,kk ,old_pix);	
	    }
	    jj++;
	  }
	}
	kk++;
      }
    }
    gdk_draw_image (gg_displaybuff ,gg_gc ,gg_image ,0,0 ,xx ,yy ,ww ,hh);
  }
}

void gg_setfrgbcolor     (double fred ,double fgreen ,double fblue)
{
  GdkColor cc;
  fred     *= 65535.0;
  fgreen   *= 65535.0;
  fblue    *= 65535.0;
  cc.red    = (guint16)fred;  
  cc.green  = (guint16)fgreen; 
  cc.blue   = (guint16)fblue;
  gdk_gc_set_rgb_fg_color (gg_gc ,&cc);
}

/************************* gg_leavegraphics  **********************/

/** Draw the pixmap on the screen
 *  Replace xlibleavegraphics
 */
void gg_leavegraphics (void)
{
  int cg = get_cg();  /* current graph */
  
  if (is_graph_hidden(cg) == FALSE) gg_draw_focus(cg);
  gg_reset_crosshair();

  gg_redraw (gg_canvas ,0 ,0 ,gg_win_w, gg_win_h);

  gdk_display_sync (gg_disp);
  gdk_flush ();
}

/************************* manage zoom_factor  **********************/

static void gg_page_zoom_set (double step)
{
  Page_geometry pg;
  Device_entry dev;
  dev = get_device_props (tdevice);
  pg = dev.pg;
  if (step != 0.0) {
    pg.width  *= step;
    pg.height *= step;
  } else {
    pg.width  = pg_width_ori;
    pg.height = pg_height_ori;
  }
  dev.pg = pg;
  set_device_props (tdevice, dev);
  gg_drawgraph ();
}

void gg_page_zoom_increase_CB (GtkWidget *w ,gpointer p)
{
  gg_page_zoom_set (ZOOM_STEP);
}

void gg_page_zoom_decrease_CB (GtkWidget *widget ,gpointer p)
{
  gg_page_zoom_set (1.0/ZOOM_STEP);
}

void gg_page_zoom_reset_CB (GtkWidget *w ,gpointer p)
{
  gg_page_zoom_set (0.0);
}

void gg_page_zoom_store_ori (void)
{
  Page_geometry pg;
  Device_entry dev;
  dev = get_device_props (tdevice);
  pg = dev.pg;
  pg_width_ori  = pg.width;
  pg_height_ori = pg.height;
}


/************************* provisoire XYCMAP **********************/ //13
void FillGTKRect (VPoint vp1 ,VPoint vp2)
{
  GdkPoint xp1 ,xp2;
  int dx ,dy;
  xp1 = gg_VPoint2GdkPoint (vp1);
  xp2 = gg_VPoint2GdkPoint (vp2);
  dx = xp2.x - xp1.x;
  dy = xp2.y - xp1.y;
  if (dx < 0) dx = -dx;
  if (dy < 0) dy = -dy;
  gdk_draw_rectangle (gg_displaybuff ,gg_gc ,TRUE ,xp1.x ,xp1.y ,dx ,dy);

}
