#include <stdio.h>
#include <math.h>
#include <limits.h>
#include <string.h>
#include "_synth.h"

// #include "rtalloc.h"
#include "vector.h"
#include "fft.h"
#include "GraphWindow.h"
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>

/* xgraph.c		graphics windows */
/* *******************************************************************
 Extension of Mixview for sound analysis and analysis Display 
		  Xavier Rodet - IRCAM
		  1.1 @(#)mvscales.c	1.1 3/20/90 90/03/20

  *******************************************************************/
/*
           Ilia Bisnovatyi - C++ conversion and marking extentions
	   5/10/1998
*/

#include <X11/Xatom.h>

#define FABS(x)       (((x)>=0.0)?x:-(x))
#define ROUND(x)      (int)((x)+0.5)


XWMHints xwmh= {
        (InputHint | StateHint),        /* flags */
        True,                           /* input */
        NormalState,                    /* initial_state */
        0,                              /* icon pixmap */
        0,                              /* icon window */
        0, 0,                           /* icon location */
        0,                              /* icon mask */
        0,                              /* Window group */
};


static char *cname[CMAX] = 
	{
		"red",
		"blue",
		"green",
		"yellow",
		"pink",
		"cyan",
		"magenta",
		"white"
	};


void x_scale(Display *dpy, int w, int h, GC scale_gc,XFontStruct *fontstructT,
	     Window a_wind,
	     int textoffset, char *unit, float x_min, float x_max,
	     int txtoffset,	/* offset of amp & time labels */
	     int dvmargin,	/* margin above & below displayed wave */
	     int p_len , int s_len , int t_len);

void y_scale(Display *dpy,int w, int h,GC scale_gc,XFontStruct *fontstructT,Window a_wind,
	     int dvoffset,int dvmargin,char *unit,float y_min, float y_max,
	     int txtoffset, Boolean trflag, int p_len , int s_len , int t_len);


static int unit_margin = 3 ;
static char *numb_example = ".12345678901234567890" ; 


//  double good_format() ;
double good_format(double timeinc, unsigned long subdiv,
		   unsigned long nb_pix_per_div, char* pformat,
		   double x_max, Boolean h_flag,
		   XFontStruct* fontstructT);

/* prints x scale under the displayed form */
void x_scale(Display *dpy, int width, int base,GC scale_gc,XFontStruct *fontstructT,Window a_wind,
	     int textoffset,char *unit,float x_min,float x_max,
	     int txtoffset,/* offset of amp & time labels */
	     int dvmargin,/* margin above & below displayed wave */
	     int p_len , int s_len , int t_len)
{
  double timeinc, logtimeinc ;
  int textloc;
  int grain,  len, toffset;
  int to_round ;
  double ntime, increment, prtime, sctime, trtime, p_inc, s_inc, t_inc;
  double diff;
  char spf[16] ;
  char string[16], * pformat=spf;

  if(x_max > x_min)
  {
    textloc = base - txtoffset;               /*  vert. loc of time chars */
    timeinc = (x_max - x_min)/width	;
	
    /* set time increment & format depending on current screen resolution */
    
    increment = good_format(timeinc, 4, 50,pformat,x_max,TRUE,fontstructT);
    p_inc = increment;
    s_inc = increment/2.0;
    t_inc = increment/10.0;
    diff = t_inc/2.0;
    
    /* set each to next highest val */
    prtime = increment * (double)(1+(long)(x_min/increment)) ;
    sctime = prtime - p_inc;
    while((sctime += s_inc) < x_min);
    if(FABS(sctime-prtime) < t_inc) sctime += s_inc;
    
    trtime = prtime - p_inc;
    while((trtime += t_inc) < x_min);
    if(trtime == prtime || trtime == sctime) trtime += t_inc;
    
    /* loop for width of current screen, check to see if next increment */
    /* is greater than primary, secondary, or tertiary hatch-times */
    for(grain = 0; grain < width; grain++)
    {	ntime = (x_min/timeinc + grain) * timeinc;
        if((ntime+timeinc) > prtime)
	{
	  XDrawLine(dpy, a_wind, scale_gc, grain, base,
		    grain, base-p_len);
	  sprintf(string, pformat, prtime);
	  len = strlen(string);
	  toffset = XTextWidth(fontstructT, string, len)/2;
	  XDrawString(dpy, a_wind, scale_gc, grain-toffset, 
		      textloc, string, len);
	  trtime = prtime + t_inc;
	  prtime += p_inc;
	  continue;
	}
	if(ntime >= sctime)
	{
	  XDrawLine(dpy, a_wind, scale_gc, grain, base,
		    grain, base-s_len);
	  trtime = sctime + t_inc;
	  sctime += p_inc;
	  continue;
	}
	if(ntime >= trtime)
	{
	  XDrawLine(dpy, a_wind, scale_gc, grain, base,
		    grain, base-t_len);
	  trtime += t_inc;
      /* temporary fix for shifty doubles */
	  if((sctime-trtime) < diff) sctime = trtime;
	  if((prtime-trtime) < diff) prtime = trtime;
	}
    }
    len = strlen(unit);
    toffset = XTextWidth(fontstructT, unit, len);
    XDrawString(dpy, a_wind, scale_gc, width-toffset-unit_margin, 
		textloc - txtoffset-unit_margin, unit, len);
  }
}
		   
double good_format(double timeinc, unsigned long subdiv,
		   unsigned long nb_pix_per_div, char* pformat,
		   double x_max, Boolean h_flag,
		   XFontStruct* fontstructT)
{
  double logtimeinc,increment, logx_max ;
  long to_round , ato_round , int_logx_max ;
  double deltat ;
  unsigned long txtwidth ; 
  
  logx_max = log10( x_max ) ;
  int_logx_max = 1+(long)logx_max ;
  if (int_logx_max <1) int_logx_max =1 ;
  
  logtimeinc = log10( deltat=(double)nb_pix_per_div * timeinc) ;
  if(logtimeinc>0.) to_round = 1. + logtimeinc ;
    else to_round =  logtimeinc ;
  if(to_round<0) ato_round = -to_round ;
    else  ato_round = to_round ;
  
  if (h_flag)
  {  
    txtwidth = XTextWidth(fontstructT, numb_example,
			  3 + int_logx_max + ato_round);
    if( nb_pix_per_div < txtwidth )
    { nb_pix_per_div = txtwidth ; 
    logtimeinc = log10( deltat=(double)nb_pix_per_div * timeinc) ;
    if(logtimeinc>0.) to_round = 1. + logtimeinc ;
    else to_round =  logtimeinc ; }
  }
  
  increment = pow(10.,(double)to_round);
  if ( deltat < increment/2. )
    if ( deltat < increment/4. )
    { increment /= 4. ; to_round -= 2 ; } 
    else
    { increment /= 2. ; to_round -= 1 ; }
  
  if(to_round<0) ato_round = -to_round ;
  else  ato_round = 0 ;
  sprintf(pformat,"%s%d.%df\0","%",int_logx_max+ato_round,ato_round);
  return (increment) ;
}

/*  trflag flagfor tertiary hash */
void y_scale(Display *dpy,int w, int height,GC scale_gc,XFontStruct *fontstructT,Window a_wind,
	     int dvoffset,int dvmargin,char *unit,float y_min, float y_max,
	     int txtoffset, Boolean trflag, int p_len , int s_len , int t_len)
{
  double y_max_y_min ;
  double increment, nextamp, maxpow = .000001;
  double p_inc, s_inc, t_inc, prval, scval, trval;
  int prloc, scloc, trloc, count; 
  char pstr[16] ;
  char string[16], * pformat=pstr ;
  int n_pticks, pr_minloc, posloc, negloc;
  long pr_minamp;
  int  len, toffset;
  
  double ampinc, scale_factor;
  
  /* set amp string format depending on current screen resolution */
  y_max_y_min = y_max - y_min ;
  if(y_max_y_min==0.)
  { y_max_y_min = 1. ;
  fprintf(stderr,
	  "Y_MIN == Y_MAX == %f !!! NO SCALING POSSIBLE!\n\r",y_min);
  }
  scale_factor = height/(y_max_y_min) ;
  ampinc = 1./scale_factor ;
  
  /* set amp increment & format depending on current screen resolution */
  increment = good_format(ampinc, 4, 50,pformat,y_max,FALSE,fontstructT);
  
  /* the minx prime amp value   */
  pr_minamp = increment * (double)((long)(y_min/increment)) ;
  if(y_min<pr_minamp) pr_minamp -= increment ;
  prval = (y_max-pr_minamp)*scale_factor; 
  
  p_inc = increment*scale_factor;
  s_inc = p_inc/2.0;
  t_inc = p_inc/10.0;
  
  trloc = ROUND(trval = prval-t_inc) ;
  scloc = ROUND(scval = prval-s_inc) ;
  prloc = ROUND(prval -= p_inc) ;
  
  /* loop for height of current band, check to see if next increment */
  /* is greater than primary, secondary, or tertiary hatch-locs */
  
  nextamp = pr_minamp+increment ;
  for(posloc=trloc ; posloc >= 1 ; posloc--)
  {
    if(posloc == prloc)
    { 
      XDrawLine(dpy, a_wind, scale_gc, 0, posloc, 
		p_len, posloc);
      sprintf(string, pformat, nextamp);
      len = strlen(string);
      XDrawString(dpy, a_wind, scale_gc, txtoffset, 
		  posloc+dvoffset, string, len);
      trloc = ROUND(trval = prval-t_inc);
      scloc = ROUND(scval = prval-s_inc);
      prloc = ROUND(prval -= p_inc);
      nextamp += increment;
      continue;
    }
    if(posloc == scloc)
    {
      XDrawLine(dpy, a_wind, scale_gc, 0, posloc,
		s_len, posloc);
      trloc = ROUND(trval = scval-t_inc);
      scloc = ROUND(scval -= p_inc);
      continue;
    }
    if(!trflag) continue;	/* no small ticks */
    if(posloc == trloc)
    {
      XDrawLine(dpy, a_wind, scale_gc, 0, posloc,
		t_len, posloc);
      trloc = ROUND(trval -= t_inc);
    }
  }
  len = strlen(unit);
  toffset = XTextWidth(fontstructT, unit, len);
  XDrawString(dpy, a_wind, scale_gc, unit_margin+p_len, 
	      txtoffset+unit_margin, unit, len);
}





GraphWindow::GraphWindow(float Srate, char *Title, float From, float To,
			 int Width, int X0, int Y0, int Height)
{
  XColor exact;
  int i;

  lines = TRUE;
  axes = TRUE;
  time = TRUE;
  spect = FALSE;
  srate = Srate;
  count = INT_MAX;
  samps = 0;
  dodisp = TRUE;
  getattr = TRUE;
  jumps = 1;
  lastMark = 0;
  
  for(i=0;i<WMAX;++i)
  {
    p[i].x = i;
    disp[i] = 0.0f;
  }
  current = 0;
  from = From; to = To;

  if(!(myDisplay= XOpenDisplay(XDisplayName(""))))
  {
    perror("Cannot open Display\n");
    return;
  }

  Screen = DefaultScreen(myDisplay); 
  cmap = DefaultColormap(myDisplay, Screen);
  for(i=0;i<CMAX;++i)
  {
    if(0==XAllocNamedColor(myDisplay, cmap, cname[i], &(color[i]),&exact ))
      pixel[i] = WhitePixel(myDisplay, Screen);
    else
      pixel[i] = color[i].pixel;
  }
  Background =  WhitePixel(myDisplay, Screen) ;
  Foreground =  BlackPixel(myDisplay, Screen) ;
  xsh = XAllocSizeHints();
  xsh->flags= (USPosition | USSize);
  xsh->height = Height;
  xsh->width = Width;
  xsh->x= X0;
  xsh->y= Y0;
  myWindow = XCreateSimpleWindow(myDisplay,
			       DefaultRootWindow(myDisplay),
			       xsh->x, xsh->y, xsh->width, xsh->height, 2, 
		               Foreground, Background) ;
  

  XSetStandardProperties(myDisplay, myWindow, Title, Title,
			 None, (char **)0, 0, xsh);
  XSetWMHints(myDisplay, myWindow, &xwmh);
  XSelectInput(myDisplay, myWindow,
	       ButtonPressMask | KeyPressMask | ExposureMask) ;
  XMapWindow(myDisplay, myWindow);
  XNextEvent(myDisplay, &Event);
  gc = DefaultGC(myDisplay, Screen) ;

  if ((fontstructT= XLoadQueryFont(myDisplay, TINY_FONT)) == NULL)
  {
    fprintf(stderr, " Display %s doesn't know font %s\n",
	    XDisplayString(myDisplay), TINY_FONT);

  }
  XSetFont(myDisplay,gc,fontstructT->fid) ;
  XSetFunction(myDisplay, gc, GXcopy);
}

GraphWindow::~GraphWindow()
{
  XFree(xsh);
}


// Added by Ilia for flexible labeling
void GraphWindow::labelGraph(char *Xl, char *Yl)
{
  strncpy(Xlabel, Xl, LABEL_SIZE-1);
  strncpy(Ylabel, Yl, LABEL_SIZE-1);
  Xlabel[LABEL_SIZE-1] = 0;
  Ylabel[LABEL_SIZE-1] = 0;
}

// Added by Ilia for text output
void GraphWindow::OutText(char *str, int X, int Y)
{
  int len, toffset;

  len = strlen(str);
  toffset = XTextWidth(fontstructT, str, len);
  
  XDrawString(myDisplay, myWindow, gc, X, Y, str, len);
  XFlush(myDisplay);
}
	      
void GraphWindow :: dispGraph(float *buf, int n, float from, float to, int colorindex)
{
  GraphWindow *o = this;
  int i;
  
  float d;

  d = 1.0f/(to-from);
  
  if(colorindex!=1)
  {
    int txtoffset = 10;     /* offset of amp & time labels */
    int dvoffset = 4, dvmargin = 6;
    int p_len = 7, s_len = 4, t_len = 2;
    XClearWindow(o->myDisplay, o->myWindow) ;
    if(o->axes)
    {
      x_scale(o->myDisplay, o->wind_att.width, o->wind_att.height, o->gc,
	      o->fontstructT, o->myWindow, txtoffset, Xlabel, 0.0f,
	      o->spect?
	          (o->time?(o->srate*0.5*o->wind_att.width/n): o->wind_att.width):
	          (o->time?(o->wind_att.width/o->srate): o->wind_att.width),
	      txtoffset, dvmargin, p_len , s_len , t_len) ;
      
      y_scale(o->myDisplay,o->wind_att.width, o->wind_att.height, o->gc,
	      o->fontstructT, o->myWindow, dvoffset, dvmargin,
	      Ylabel, from, to, txtoffset, TRUE, p_len , s_len , t_len);
    }
  }
  else
    if (colorindex==-1)
    {
      colorindex=0; XClearWindow(o->myDisplay, o->myWindow);
    }
  
  XSetForeground(o->myDisplay,o->gc, o->pixel[colorindex%CMAX]);
  for(i=0;i<n;++i)
  {
    o->p[i].y = o->wind_att.height-((buf[i]-from)*d*o->wind_att.height);
  }
  if(o->lines)
    XDrawLines(o->myDisplay,o->myWindow ,o->gc, o->p, n, CoordModeOrigin);
  else
    XDrawPoints(o->myDisplay,o->myWindow ,o->gc, o->p, n, CoordModeOrigin);
  
  XFlush(o->myDisplay);
  
}

void GraphWindow::GraphTimemode(Boolean Time)
{
	time = Time;
}

void GraphWindow::GraphSpect(float *wind, int npoints)
{
	if(npoints>MAXWIN)
		npoints = MAXWIN;
	if(!wind)
	{
		RvecBhwind(npoints, bh, 1, 3);				
		fftwindow = bh;
	}
	else
		fftwindow = wind;
	spect = TRUE;
	npoints = npoints;
}

#define LMIN -110.0f
static int nearpow2(int n)
{
  int j;
  for(j=1; j<n; j*= 2);
  return j;
}
		
void GraphWindow :: cGraph(int n, float *d, int stride, int colorindex)
{
  int i;

  for(i=0;i<n;++i)
  {
    disp[(current)++] = d[i*stride];    
    if((samps % 32768)==0)
      getattr = TRUE;
    if((samps % jumps) == 0)
    {
      dodisp = TRUE;
      if(getattr)
      {
	XGetWindowAttributes(myDisplay, myWindow, &(wind_att)) ;
	getattr = FALSE;
      }
    }
    ++(samps);
    if(spect)
    {
      if((current>=npoints || current>=WMAX))
      {
	/* compute fftsize as a function of width */
	int fftsize = 2*nearpow2(wind_att.width);
	/*	float *SPEC = spec;   */
	short j;
	if(fftsize>WMAX)
	  fftsize = WMAX;
	if(fftsize>=current && dodisp)
	{
	  kvec(0.0f, 2*WMAX, spec, 1);
	  assignvec(npoints,spec,1,disp,1);
	  mulvec(npoints, spec, 1, spec, 1, fftwindow, 1);
	  
	  fftRealfast(fftsize,spec);
	  for(j=0;j<fftsize/2;++j)
	  {
	    float v = spec[2*j]*spec[2*j] +spec[2*j+1]*spec[2*j+1];
	    if(v<1.0e-12)
	      spec[j] = LMIN;
	    else
	      
	      spec[j] = 0.5f*20.0f*log10(v);
	  }
	  dispGraph(spec, fftsize/2, LMIN, 10.0f, 0);
	  dodisp = FALSE;
	}
	current = 0;
      }
    }
    else
    {
      if(current >= wind_att.width || current >= WMAX)
      {
	if(dodisp)
	  dispGraph(disp, wind_att.width, from, to, colorindex);
	dodisp = FALSE;
	current = 0;
      }
    }
    
	}
}

void GraphWindow::Graph(int n, float *d, int stride)
{
  cGraph(n, d,  stride, 0);
}

#define markH 7

void GraphWindow::markOnGraph(int n)
{
  int py;
  py = p[n].y;
  XSetForeground(myDisplay, gc, pixel[0]);
  XDrawLine(myDisplay, myWindow, gc, n, py-markH, n, py+markH);
}


void GraphWindow::markOffGraph(int n)
{
  int py;
  py = p[n].y;
  XSetForeground(myDisplay, gc, WhitePixel(myDisplay, Screen));
  XDrawLine(myDisplay, myWindow, gc, n, py-markH, n, py-1);
  XDrawLine(myDisplay, myWindow, gc, n, py+markH, n, py+1);
}


void GraphWindow::markGraph(int n)
{
  if ((n>0) && (n<xsh->width))
  {
    markOffGraph(lastMark);
    markOnGraph(n);
    XFlush(myDisplay);
    lastMark = n;
  }
}


void GraphWindow :: GraphBang(float *count)
{
	jumps = *count;
}







