/** File zc_contours.c
 *
 *  Contour curves are computed using algorithm toms626
 *  (www.netlib.org) by A. Preusser in 1984
 *
 *  NOT YET DONE
 *  The original Fortran 77 file had been splitted and
 *  recasted in a  more modern Fortran 95 free form 
 *  with few enhancements: see ../contours  directory
 * 
 *  The call of subroutine tricp with mode=0 computes an interpolating
 *  triangular mesh of the surface in arrays iwork[] and dwork[], then
 *  contour curves are drawn via plot_callback calls.
 *  
 *  Note that the curves are drawn by fragments and there is no arrays
 *  of connected points corresponding to the curves. Hopefully the
 *  last stage (scanning the mesh to draw curves) is not the more time
 *  consuming and keeping arrays iwork[] and dwork[] and calling tricp
 *  with mode=3 allows to redraw the plot without too much time.
 */



#include <stdio.h>
#include <string.h>
#include <cmath.h>

#include <gtk/gtk.h>


#include "globals.h"

#include "draw.h"
#include "graphs.h"
#include "utils.h"
#include "noxprotos.h"
#include "device.h"

#include "gg_gutil.h"
#include "gg_protos.h"

#include "ng_objects.h"
#include "nn_tree.h"

#include "zc.h"

extern void sprintf_with_niceprec (char *buf ,double x ,double x1 ,double x2);

/* ---------------------- C O N T O U R S ---------------------- */

#define MAXPICKDIST2 1.e-5      /* the square of maximum distance away from an object */

void zc_label_draw_zclist  (ZcLabel *zclist);

static int place_label = FALSE;

#if defined (HAVE_CONTOURS)

#define NEED_F77_UNDERSCORE 1

#ifdef NEED_F77_UNDERSCORE
#  define F77_FNAME(fname) fname ## _
#else
#  define F77_FNAME(fname) fname
#endif

void F77_FNAME (tricp) (double *, double *, double *, int *, double *, int *, double *, int *, int *);


/* Contour type */
typedef enum {
  CONTOUR_VALUE_AUTOMATIC = 0
  ,CONTOUR_VALUE_REGULARLY_SPACED
  ,CONTOUR_VALUE_LIST                    /* Explicit list of contour z-values.  */
} ContourValue;

/* Contour spacing */
typedef enum {
  CONTOUR_SPACING_LINEAR = 0
  ,CONTOUR_SPACING_LOGARITHMIC
  ,CONTOUR_SPACING_RECIPROCAL
} ContourSpacing;


static double xpt ,ypt;     /* communication between zc_label_place and plot_callback */
static double *x 	= NULL;
static double *y 	= NULL;
static double *z 	= NULL;

static int numpoints;   /* nb of pts in contour curves. */ 
/* Allocations with numpoints are done  to a very large sup : 
 * the number of points in the set */

/* We copy the plotarr of the current set locally.
 * Note that the pointers allways  links to the current arrays for sets
 */
static int gno ,setno ,igno;
static plotarr p;
static int      nticks;      /* in the colorbar and nb au levels for contours */
static int      z_scaled;    /* TRUE if a z-scale had been computed */
static double   *zlevels;    /* array of the values for ticks and contour level curves */
static int      hidden;      /* Contours not computed or hidden. For colorbar see objs[icolorbar] */
static int      dirty;       /* Contours need to be (re)computed */
static int      mode;        /* modes to call tricp */
static int      *iwork;      /* for tricp */
static double   *dwork;      /* for tricp */
static int      ctype;       /* ContourValue   : CONTOUR_VALUE_AUTOMATIC ,... */
static int      spacing;     /* ContourSpacing : CONTOUR_SPACING_LINEAR ,... */
static double   z1 , z2;     /* first and last levels values */
static double   *listz;      /* to use a specific list of values for z levels (not yet implemented) */
static int      listz_len;
static ZcLabel  *zclist;     /* a list of labels to print */
static double   charsize;    /* character size of contour labels are copied from avalue */
static int      font;        /* "         font   "          "                  "        */
static int      color;       /* "         color  "          "                  "        */

int zc_make_current (int gno_in ,int setno_in)
{
  if (get_graph_plotarr (gno_in ,setno_in ,&p) == RETURN_SUCCESS) {
    gno   = gno_in;
    setno = setno_in;
    igno = obj_tid_get_graph_num (gno);
    if (igno < 0) {
      errmsg ("zc_make_current : igno < 0 internal error");
      return FALSE;
    }
    Contour *pc = &(p.contour);
    /* get data */
    x		= getcol(gno, setno, DATA_X);
    y		= getcol(gno, setno, DATA_Y);
    z		= getcol(gno, setno, DATA_Y1);
    numpoints	= p.data.en[DATA_Y1];
    nticks	= pc->nticks;
    z_scaled	= pc->z_scaled;
    zlevels	= pc->zlevels;
    hidden	= pc->hidden;
    dirty	= pc->dirty;
    mode	= pc->mode;
    iwork	= pc->iwork;
    dwork	= pc->dwork;
    ctype	= pc->type;
    spacing	= pc->spacing;
    z1		= pc->z1;
    z2		= pc->z2;
    listz	= pc->listz;
    listz_len	= pc->listz_len;
    zclist	= pc->zclist;
    /* use avalue values for contour label characters */
    charsize  = p.avalue.size;
    font      = p.avalue.font;
    color     = p.avalue.color;
    return TRUE;
  }
  return FALSE;
}

void zc_update_plotarr (void)
{
  if (is_valid_setno (gno ,setno)) {
    Contour *pc = &(p.contour);  /* to lighten lines below */
    pc->nticks    = nticks;
    pc->z_scaled  = z_scaled;
    pc->zlevels	  = zlevels;
    pc->hidden	  = hidden;
    pc->dirty	  = dirty;       
    pc->mode	  = mode;        
    pc->iwork	  = iwork;       
    pc->dwork	  = dwork;       
    pc->type	  = ctype;       
    pc->spacing	  = spacing;     
    pc->z1	  = z1;          
    pc->z2	  = z2;          
    pc->listz	  = listz;       
    pc->listz_len = listz_len;   
    pc->zclist    = zclist;      
    set_graph_plotarr (gno ,setno ,&p);
  }
}


/**
 * Create a new z-label in pc->zclist
 */
void zc_label_new (int gno ,int setno ,double x ,double y ,double z ,double radians)
{
  ZcLabel *zcl ,*zcln;
  zc_make_current (gno ,setno);
  if (zclist == NULL) {
    zclist          = malloc (sizeof (ZcLabel));
    zclist->x       = x;
    zclist->y       = y;
    zclist->z       = z;
    zclist->radians = radians;
    zclist->next    = NULL;
    /* zclist had been updated only locally, thus : */
    zc_update_plotarr ();
  } else {
    /* we go to the end of the existing list */
    zcl  = zclist;
    zcln = zclist->next;
    while (zcln != NULL) {
      zcl  = zcln;
      zcln = zcln->next;
    }
    /* and create the new element */
    zcln = malloc (sizeof (ZcLabel));
    zcl->next  	  = zcln;
    zcln->x       = x;
    zcln->y       = y;
    zcln->z    	  = z;
    zcln->radians = radians;
    zcln->next    = NULL;
  }
}



/* */
void F77_FNAME (error_callback) (char *str, int len)
{
#define MSG "Error in contour plot: "
#define MSG_LEN (sizeof (MSG) - 1)
  char * s;

  s = (char *) xmalloc (len + MSG_LEN);
  if (s == NULL)
  {
    errmsg ("Can't xmalloc in error_callback_");
    return;
  }

  strncpy (s, MSG, MSG_LEN);
  strncpy (s + MSG_LEN, str, len);
  s[MSG_LEN + len] = '\0';
  errmsg (s);

  xfree (s);
#undef MSG
}

/**
 * n=2 means     move to x,y with pen down
 *   3 means     move to x,y with pen in upward position
 */
static double lastx, lasty;
void F77_FNAME (plot_callback) (double *xx, double *yy, int *n ,int *cb_found ,double *level)
{
  WPoint wp;
  VPoint vp1, vp2;
  double dx ,dy ,distc1 ,distc2;

  if (*n == 2) {
    wp.x = lastx;
    wp.y = lasty;
    vp1 = Wpoint2Vpoint (wp);
    wp.x = *xx;
    wp.y = *yy;
    vp2 = Wpoint2Vpoint (wp);
    if (place_label) {
      dx = vp1.x - xpt;
      dy = vp1.y - ypt;
      distc1 = dx*dx + dy*dy;
      dx = vp2.x - xpt;
      dy = vp2.y - ypt;
      distc2 = dx*dx + dy*dy;
      if (distc1 < MAXPICKDIST2 || distc2 < MAXPICKDIST2) {
 	zc_label_new (gno ,setno ,*xx ,*yy ,*level ,0.0);
 	*cb_found = 1;
      }
    } else {
      DrawLine (vp1, vp2);
      *cb_found = 0;
    }
  }
  lastx = *xx;
  lasty = *yy;
}

/**
 *  Compute the contour curves by call to tricp
 */
void zc_draw_set_xycontours (int gno_in ,int setno_in) 
{
  double min ,max ,delta;
  int i;
  if (zc_make_current (gno_in ,setno_in)) {
    place_label = FALSE;
    setclipping	(TRUE);
    /* draw the contour lines */
    if (dirty && iwork == NULL) {
      mode  = 0;
      dwork = xmalloc (5  * numpoints * sizeof (double));
      iwork = xmalloc (31 * numpoints * sizeof (int));
    } else if (dirty) {
      mode  = 0;
      dwork = xrealloc (dwork ,5  * numpoints * sizeof (double));
      iwork = xrealloc (iwork ,31 * numpoints * sizeof (int));
    } else {
      mode = 3;
    }
    /* Determine the values of contours */
    zlevels = NULL;
    switch (ctype) {
    case CONTOUR_VALUE_AUTOMATIC:          //14  Not yet implemented.  Falling back to next case.  
    case CONTOUR_VALUE_REGULARLY_SPACED:
      zlevels = (double *) xmalloc (nticks * sizeof (double));
#define OP(x)  (spacing == 0 ? (x) : (spacing == 1 ? log(x) : 1/(x)))
#define ROP(x) (spacing == 0 ? (x) : (spacing == 1 ? exp(x) : 1/(x)))
      if (z_scaled == FALSE) {
	min =  MIN2 (OP(z1) ,OP(z2));
	max =  MAX2 (OP(z1) ,OP(z2));
	z1 = min;
	z2 = max;
      } else {
	min = z1;
	max = z2;
      }
      delta = (max - min) / (nticks - 1);
      for (i = 0; i < nticks; i++)
	zlevels[i] = ROP(min + i * delta);
#undef OP
#undef ROP
      break;
    case CONTOUR_VALUE_LIST:           //14 a faire
      break;
    }
  /* reset gc */
  devsetfrgbcolor (0. ,0. ,0.);
  setcolor (1);
    /* Draw contour lines  */
    setlinewidth (p.linew);
    setlinestyle (p.lines);          /* does not work, points too close */
    setcolor     (p.contour.line_color);
    F77_FNAME (tricp) (x ,y ,z ,&numpoints ,zlevels ,&nticks ,dwork ,iwork ,&mode);
    z_scaled = TRUE;
    if (zlevels != NULL) {
      zc_label_draw_zclist (zclist);
    }
    dirty = FALSE;
    zc_update_plotarr ();
  }
}


/**
 *  Find the place and the value of label via plot_callback ()
 *  and add it to zclist
 *  The set is the one selected in ge_tree
 */
int zc_label_place (int gno_in ,int setno_in ,VPoint vp ,int on)
{
  if (zc_make_current (gno_in ,setno_in)) {
    if (zlevels == NULL || iwork == NULL) {
      errmsg("zc_label_place : you must compute contour curves BEFORE placing labels");
      return FALSE;
    }
    if (on) {
      xpt  	  = vp.x;           /* to be used by plot_callback */
      ypt  	  = vp.y;           /* to be used by plot_callback */
      mode        = (z_scaled && iwork != NULL) ? 3 : 0;
      place_label = TRUE;
      F77_FNAME (tricp)    (x ,y ,z ,&numpoints ,zlevels ,&nticks ,dwork ,iwork ,&mode); 
      zc_label_draw_zclist (zclist);
      zc_update_plotarr    (); 
    } else {
      place_label = FALSE;
    }
  }
  return TRUE;
}

/**
 * Detect if a contour label is clicked
 */
static int zc_label_selected (ZcLabel *zcl ,VPoint vp)
{
  double xtmp ,ytmp ,dx ,dy ,distc;
  if (zcl != NULL) {
    world2view (zcl->x ,zcl->y ,&xtmp ,&ytmp);
    dx = vp.x - xtmp;
    dy = vp.y - ytmp;
    distc = dx*dx + dy*dy;
    return (distc < 4 * MAXPICKDIST2);
  } else {
    return FALSE;
  }
}

/**
 *  Kill the contour label pointed by vp
 */
void zc_label_kill  (int gno_in ,int setno_in ,VPoint vp)
{
  ZcLabel *zcl ,*zcln ,*zclp;
  if (zc_make_current (gno_in ,setno_in)) {
    if (zlevels == NULL || iwork == NULL) {
      errmsg("zc_label_kill : you must compute contour curves BEFORE");
      return;
    }
    if (zclist == NULL) {
      errmsg("zc_label_kill : label list empty");
      return;
    }
    zcl = zclist;
    if (zc_label_selected (zcl ,vp)) {
      p.contour.zclist = zcl->next;
      xfree (zcl);
      set_graph_plotarr (gno ,setno ,&p);
      gg_drawgraph ();
    } else {
      zclp = zcl;
      zcl = zcl->next;
      while (zcl != NULL) {
	zcln = zcl->next;
	if (zc_label_selected (zcl ,vp)) {
	  xfree (zcl);
	  zclp->next = zcln;
	  gg_drawgraph ();
	  return;
	} else {
	  zclp = zcl;
	  zcl  = zcl->next;
	}
      }
    }
  }
}

/**
 * Free all the list of contour labels
 */
void zc_label_free_list (int gno ,int setno)
{
  ZcLabel *zcl ,*zcln;
  plotarr p;
  if (get_graph_plotarr (gno ,setno ,&p) == RETURN_SUCCESS) {
    zcl = p.contour.zclist;
    while (zcl != NULL) {
      zcln = zcl->next;
      xfree (zcl);
      zcl = zcln;
    }
    p.contour.zclist = NULL;
    set_graph_plotarr (gno ,setno ,&p);
  }
}

/**
 * Draw one contour label
 */
static void zc_label_draw_label (ZcLabel *label)
{
  char buf[32];
  VPoint vp1 ,svp1 ,svp2;
  view   sbb;
  double degres;
  char str[MAX_STRING_LENGTH];
  world2view (label->x ,label->y ,&vp1.x ,&vp1.y);
  strcpy (str, p.avalue.prestr);
  sprintf_with_niceprec (buf ,label->z ,z1 ,z2);
  strcat (str ,buf);
  strcat (str, p.avalue.appstr);

  if (label->radians >  0.5 * M_PI) label->radians += -M_PI;
  if (label->radians < -0.5 * M_PI) label->radians +=  M_PI;
  degres = label->radians * 180.0 / M_PI;
  t1fonts_rasterize_only (TRUE);
  /* Draw background box */
  activate_bbox (BBOX_TYPE_TEMP, TRUE);
  reset_bbox    (BBOX_TYPE_TEMP);
  WriteString (vp1 ,0 ,JUST_CENTER | JUST_MIDDLE ,str);
  sbb = get_bbox (BBOX_TYPE_TEMP);
  view_extend (&sbb ,getlinewidth());
  view_rotate (&sbb ,label->radians);
  svp1.x = sbb.xv1;
  svp1.y = sbb.yv1;
  svp2.x = sbb.xv2;
  svp2.y = sbb.yv2;
  setcolor   (0);
  setpattern (1);
  FillRect (svp1, svp2);
  t1fonts_rasterize_only (FALSE);
  /* Draw the string */
  setcolor (color);
  setcharsize (charsize);
  setfont     (font);
  WriteString (vp1 ,(int)degres ,JUST_CENTER | JUST_MIDDLE ,str);
}


/**
 *  (re)draw all labels in zclist
 */
void zc_label_draw_zclist (ZcLabel *zclist)
{
  ZcLabel *zcl;
  zcl = zclist;
  while (zcl != NULL) {
    zc_label_draw_label (zcl);
    zcl = zcl->next;
  }
}

#else  /* HAVE_CONTOURS not true */

/* Dummy interface */
int zc_make_current (int gno_in ,int setno_in)
{ return FALSE;}
void zc_update_plotarr (void)
{}
void zc_label_new (int gno ,int setno ,double x ,double y ,double z ,double radians)
{}
void zc_draw_set_xycontours (int gno_in ,int setno_in) 
{}
int zc_label_place (int gno_in ,int setno_in ,VPoint vp ,int on)
{ return FALSE;}
void zc_label_kill  (int gno_in ,int setno_in ,VPoint vp)
{}
void zc_label_free_list (int gno ,int setno)
{}
void zc_label_draw_zclist (ZcLabel *zclist)
{}

#endif  /* HAVE_CONTOURS */
