#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <GL/glut.h>
#include <GL/gl.h>
#include <pthread.h>


#include "mrpc.h"   // multires peak cache object
#include "debug.h"  // debugging macros
#include "macros.h" // other util macros
#include "widget.h" 

/* WAVE EDITING WIDGET */

void wvvw::resize(int x, int y, int width, int height){
  this->widget::resize(x,y,width,height);
}

wvvw::~wvvw(){
  if (this->cache) delete this->cache;
}
wvvw::wvvw(void *receiver) {

  widget::widget();

  this->vec = 0;            
  this->elements = 0;
  ASSERT(receiver);
  this->receiver = receiver;
  this->cache = 0;
  this->sel_start = 0;
  this->sel_endx = 0;
  this->view_start = 0;
  this->view_endx = elements;

  this->motion_handler = &wvvw::dummy_motion_handler;
  this->zoom = 1.0; // full scale
  
  this->press_zoom = 1.0;
  this->gain = 1.0;

  this->point = -1; //disabled

  this->waveform_color[0] = 1.0f;
  this->waveform_color[1] = 1.0f;
  this->waveform_color[2] = 1.0f;
  this->waveform_color[3] = 1.0f;

  this->selection_color[0] = 0.1f;
  this->selection_color[1] = 0.2f;
  this->selection_color[2] = 0.3f;
  this->selection_color[3] = 1.0f;

  this->needle_color[0] = 1.0f;
  this->needle_color[1] = 0.0f;
  this->needle_color[2] = 0.0f;
  this->needle_color[3] = 1.0f;

}

void wvvw::set_array(float *f, int elements){
  ASSERT(f);
  ASSERT(elements);
  this->clear_cache();
  this->vec = f;
  this->elements = elements;
  this->set_view(0, elements);
  this->set_selection(0, 0);
  //fprintf(stderr, "set_array %x %d\n", f, elements);
}
void wvvw::draw_needle(void){
    if (this->point >= 0){
	double fneedle = 
	    ((double)(this->point - this->view_start))  
	    / ((double)(this->view_endx - this->view_start));

	glColor4fv(needle_color);
	glBegin(GL_LINES); 
	{
	    glVertex2f(fneedle, 0);
	    glVertex2f(fneedle, 1);
	}
	glEnd(); 
	
    }
}


// display widget in subframe
void wvvw::draw(void){
  //fprintf(stderr, "%d %d %d %d\n", this->sel_start, this->sel_endx, this->view_start, this->view_endx);

  if (!this->vec) { // if no array, send a request to app
      event_t e;
      e.type = REQ_ARRAY;
      this->send_app(&e);
      return;
  }

  this->widget::realestate(); // setup viewport

  //this->frame_width /= 2;

  if (this->update_cache()){         // make sure array and cache are in place
    this->draw_waveform();
    this->draw_selection();  
    this->draw_needle();  
  }

  //this->frame_width *= 2;

}


/*
  mouse event handler
  motion:
  left button = selection start
  right button = selection end
  middle button: x = move point, y = zoom

*/

double wvvw::dpoint(double fraction){
    return dinterpolate(this->view_start, this->view_endx, fraction);
}




/*
  set/get operations preserving invariants:
  there's no fine grained locking because of the spaghetti factor.

  valid selection
  valid view (samples per pixel >= 1.0)
*/

void wvvw::set_selection(int start, int endx){
    start = clip(start, 0, this->elements);
    endx  = clip(endx , 0, this->elements);
    if (endx < start) endx = start;
    this->sel_start = start;
    this->sel_endx = endx;
}


// map current view to zoom
float wvvw::unzoom(void){
  if ((this->frame_width <= 0)
      ||(this->elements <= 0)) return 1.0f;

    double fullscale = ((double)this->elements) / ((double)this->frame_width);
    double samples_per_pixel = ((double)(this->view_endx - this->view_start)) / ((double)this->frame_width);
    double zoom = log(samples_per_pixel) / log(fullscale);
    ASSERT(samples_per_pixel >= 1.0);
    ASSERT(zoom >= 0.0);
    ASSERT(zoom <= 1.0);
    return zoom;
}


void wvvw::set_view(int start, int endx){
  if (this->frame_width <= 0) return;

    /* limit zoom to 1 sample/pixel */
    int min_endx = start + this->frame_width;
    endx = max(min_endx, endx);

    this->view_start = start;
    this->view_endx = endx;
    this->zoom = this->unzoom();
}





// clear cache
void wvvw::clear_cache(void){
    if (this->cache) delete this->cache;
    this->cache = 0;
}

// make sure theres a cache present
int wvvw::update_cache(void){

    /* no vector? do nothing */
    if (!this->vec) return 0;

    /* check if we have one 
       cache should be deleted by any operation
       that modifies the array (no incremental update of cache)
     */
    if (this->cache) return 1;

    /* build new */
    this->cache = new mrpc(this->vec, this->elements, 4, 16);
    ASSERT(this->cache);
    return 1;
}


// map 'highlevel' zoom to samples/pixel : (fullscale)^zoom
float wvvw::zoom_to_spp(float zoom){
  zoom = fclip(zoom, 0, 1);
  float fullscale = fmax(1.0f, ((float)this->elements) / ((float)this->frame_width));
  //fprintf(stderr, "elements %d, width %d\n", this->elements, this->frame_width);
  //fprintf(stderr, "fs %f, zoom %f\n", fullscale, zoom);
  return pow(fullscale, zoom);
}

// draw a single peak line (min / max)
void wvvw::draw_peakline(float *f, int stride){
    int i;
    glColor4fv(this->waveform_color);
    glBegin(GL_LINE_STRIP);
    for (i=0; i<this->frame_width; i++){
      float x = f[0] * this->gain;
      glVertex2f(((float)i) / ((float)this->frame_width), (x + 1.0f) * 0.5f);
      f += stride;
    }
    glEnd();
}

void wvvw::draw_waveform_lines(pair_t *peaks){
    this->draw_peakline(&peaks->min, 2);
    this->draw_peakline(&peaks->max, 2);
}


// get peak array and draw
void wvvw::draw_waveform(void){

    if (this->frame_width <= 0) return; // no valid window

    int nb_peaks = this->frame_width;    ASSERT(nb_peaks);
    pair_t p[nb_peaks]; // peaks to draw

    ASSERT(this->cache); // make sure we have a cache

    // get peak array
    this->cache->peaks(this->view_start, this->view_endx, p, nb_peaks);

    // draw it
    this->draw_waveform_lines(p);

}


// draw waveform selection rectangle
void wvvw::draw_selection(void){
    double fsel_start = 
      ((double)(this->sel_start - this->view_start)) 
      / ((double)(this->view_endx - this->view_start));

    double fsel_endx  = 
      ((double)(this->sel_endx  - this->view_start)) 
      / ((double)(this->view_endx - this->view_start));

    glColor4fv(this->selection_color);

    glEnable(GL_COLOR_LOGIC_OP);
    glLogicOp(GL_OR);
    glBegin(GL_QUADS); 
    {
	glVertex2f(fsel_start, 0);
	glVertex2f(fsel_endx, 0);
	glVertex2f(fsel_endx, 1);
	glVertex2f(fsel_start, 1);
    } 
    glDisable(GL_COLOR_LOGIC_OP);
    glEnd();
}

void wvvw::dummy_motion_handler(event_t *e){}

void wvvw::left_motion_handler(event_t *e){
  int p = (int)this->dpoint(e->e.mouse.x); // use x coordinate to interpolate view
  p = clip(p, 0, this->elements);  // clip to valid range
  if (p > this->sel_endx) this->sel_endx = p;    // check order
  this->sel_start = p;
}
void wvvw::right_motion_handler(event_t *e){
    int p = (int)this->dpoint(e->e.mouse.x);
    p = clip(p, 0, this->elements);    
    if (p < this->sel_start) this->sel_start = p;
    this->sel_endx = p;
}



void wvvw::middle_motion_handler(event_t *e){

    /* compute zoom : 
         (old) map (bottom->top) == (0->1) to (1 -> full_level) 
	 similar relative map */

    this->zoom = fclip(this->press_zoom + (e->e.mouse.y - this->press_y)/4, 0, 1);
    double scale = this->zoom_to_spp(this->zoom);

    //fprintf(stderr, "zoom %f, scale = %f\n", this->zoom, scale);

    /* point from click */
    int point = (int)this->press_point;
 
    /* compute new viewport */
    double range = ((double)(this->frame_width)) * scale;
    int o1 = (int) (range * e->e.mouse.x);
    int o2 = (int) (range - o1);
    int view_start = point - o1;
    int view_endx = point + o2;
    this->set_view(view_start, view_endx);

}


/* event handler
   left button = selection start
   right button = selection end
   middle button = move (x) / zoom (y)
*/

void wvvw::set_point(int index){
  if (index < 0) index = 0;
  if (index >= this->elements) index = this->elements;
  this->point = index;
}

void wvvw::center(void){
  if (this->point < 0) return;
  int length = this->view_endx - this->view_start;
  int start = this->point - (length / 2);
  this->set_view(start, start + length);
}

void wvvw::event(event_t *e){

    switch(e->type){
    case SELECTION: this->set_selection(e->e.range.start, e->e.range.start + e->e.range.length); return;
    case VIEW:      this->set_view(e->e.range.start, e->e.range.start + e->e.range.length); return;
    case ARRAY:     this->set_array(e->e.array.vec, e->e.array.elements); return;
    case POINT:     this->set_point(e->e.i); return;
    case CENTER:    this->center(); return;
    case REDRAW:    this->draw(); return;

    case PRESS:
	/* store coordinates */
	this->press_x = e->e.mouse.x;
	this->press_y = e->e.mouse.y;
	this->press_point = (int)dinterpolate(this->view_start, this->view_endx, e->e.mouse.x);
	this->press_zoom = this->zoom;
\
	/* link handler */
	switch(e->e.mouse.button){
	case 0: this->motion_handler = &wvvw::left_motion_handler; break;
	case 2: this->motion_handler = &wvvw::right_motion_handler; break;
	case 1: this->motion_handler = &wvvw::middle_motion_handler; break;
	default: this->motion_handler = &wvvw::dummy_motion_handler; break;

	case 4: this->gain *= (95.0 / 100.0); return; // don't call motion handler for these
	case 3: this->gain *= (100.0 / 95.0); return;
	}
	/* fallthrough */
    case MOTION:
        (this->*motion_handler)(e);
	break;

    case KEY_PRESS:
	//post("key %d", e->key);
	switch(e->e.keyboard.key){
	case '0':
	case '1':
	case '2':
	case '3':
	case '4':
	case '5':
	case '6':
	case '7':
	case '8':
	case '9':
	default:
	  e->type = OUTLET_RANGE; // send current range to app
	  e->e.range.start = this->sel_start;
	  e->e.range.length = this->sel_endx - this->sel_start;
	  this->send_app(e);
	  break;

	case 'r':  /* refresh cache on next redraw */
	    this->clear_cache();
	    break;
	case 'a':  /* select all */
	    this->sel_start = 0;
	    this->sel_endx = this->elements;
	    break;
	case 'f':  /* full view */
	    this->set_view(0, this->elements);
	    break;
	case 's': /* selection view */
	    if (this->sel_start != this->sel_endx){ // ignore if no selection
		this->set_view(this->sel_start, this->sel_endx);
	    }
	    break;
	case 'F':
            glutFullScreen();  // FIXME: deglutify widget.cc
	    break;
	case 'm': /* move selection */
	{
	    int length = this->sel_endx - this->sel_start;
	    int start = (int)dinterpolate(this->view_start, this->view_endx, e->e.keyboard.x);
	    this->set_selection(start, start+length);
	}
	    break;
	    
	}
    }

}

void wvvw::send_app(event_t *e){
  e->sender = this;
  e->receiver = this->receiver;
  app_send(e);
}




