#include <string.h>
#include "Plot.H"
#include "sfont.h"
#include "shmem.h"

int Plot_add_drawer( Fl_Plot *plot,
			void (*func)( Fl_Plot *, void *obj, void *arg ),
			void *obj, void *arg, char *name, int id ) {
  plot->add_drawer( func, obj, arg, name, id );
}

int Plot_inpick( Fl_Plot *plot ) { return plot->inpick(); }

void Plot_setpick( Fl_Plot *, void (*pickcb)(Fl_Plot *, int hits, int ents, GLuint *buf) );

Fl_Plot::Fl_Plot(int x, int y, int w, int h, const char *label)
		: Fl_Gl_Window(x,y,w,h,label) {
  init();
  end();
}


int Fl_Plot::add_drawer( void (*func)( Fl_Plot *, void *obj, void *arg ),
				void *obj, void *arg, const char *name, int id )
{
  if(id < 0) id = next_id();
  int dno = withid( id );
  if(dno < 0) {
    if(ndrawers_ >= maxdrawers_) {
	maxdrawers_ = ndrawers_*2 + 15;
	int room = maxdrawers_ * sizeof(struct drawer);
	drawers_ = (struct drawer *)
		    (drawers_==NULL ? malloc(room) : realloc(drawers_, room));
    }
    dno = ndrawers_++;
  }
  struct drawer *dp = &drawers_[dno];
  dp->func = func;
  dp->obj = obj;
  dp->arg = arg;
  dp->name = shmstrdup(name);
  dp->id = id;
  notify();
  redraw();
  return id;
}

void Fl_Plot::notify() {
  if(notify_ != NULL)
    (*notify_)( this, notifyarg_ );
}


int Fl_Plot::withid( int id ) const {	// Which drawer[] slot is id in?
  for(int dno = 0; dno < ndrawers_; dno++)
    if(drawers_[dno].id == id)
	return dno;
  return -1;
}

int Fl_Plot::next_id() const {
  int id;
  for(id = 1; withid(id) >= 0; id++)
    ;
  return id;
}

void draw_axis( float v0, float v1, float ybase, float ytick, float htext, char *just, char *title ) {
  glColor3f( 1,1,1 );
  glBegin( GL_LINES );
  glVertex2f( 0,ybase );
  glVertex2f( 1,ybase );
  glVertex2f( 0,ybase );
  glVertex2f( 0,ytick );
  glVertex2f( 1,ybase );
  glVertex2f( 1,ytick );
  glEnd();
  char lbl[16];
  sprintf(lbl, "%.2g", v0);
  Point at = { 0, ytick, 0 };
  sfStrDrawTJ( lbl, htext, &at, NULL, just );

  sprintf(lbl, "%.2g", v1);
  at.x[0] = 1;
  sfStrDrawTJ( lbl, htext, &at, NULL, just );

  if(title) {
    at.x[0] = .5;
    at.x[1] = ytick + (ytick-ybase);
    sfStrDrawTJ( title, htext, &at, NULL, "c" );
  }
}

void Fl_Plot::draw() {
  /* draw scene */

  if(!valid() || damage() || inpick()) {
    /* Assume reshaped */
    valid(1);
    glViewport( 0, 0, w(), h() );
  }

  glClearDepth( 1.0 );
  glColorMask( 1, 1, 1, 1 );
  glClearColor( bgcolor_.x[0], bgcolor_.x[1], bgcolor_.x[2], 0 );
  glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

  glDisable( GL_DEPTH_TEST );
  glDisable( GL_LIGHTING );
  glDisable( GL_TEXTURE_2D );
  glDisable( GL_COLOR_MATERIAL );

  glMatrixMode( GL_PROJECTION );
  glLoadIdentity();
  if(inpick()) {
    GLint vp[4] = {0, 0, w(), h()};
    gluPickMatrix( pickx_, picky_, pickwidth_, pickheight_, vp );
  }
  glOrtho( -.15, 1.05, -.15, 1.05, -1, 1 );
  glMatrixMode( GL_MODELVIEW );

  glPushMatrix();
  glScalef( x1_ == x0_ ? 1 : 1/(x1_-x0_),
	    y1_ == y0_ ? 1 : 1/(y1_-y0_), 1 );
  glTranslatef( -x0_, -y0_, 0 );
  for(int i = 0; i < ndrawers_; i++) {
    struct drawer *dp = &drawers_[i];
    if(dp->func != NULL) {
	if(inpick_) {
	    glLoadName( dp->id );
	    glPushName( 0 );
	}
	(*dp->func)( this, dp->obj, dp->arg );
	if(inpick_)
	    glPopName();
    }
  }
  glPopMatrix();

  /* Draw axes */

  draw_axis( x0_, x1_, -.01, -.04, .1, "n", xtitle() );
  glPushMatrix();
  glRotatef( 90, 0,0,1 );
  draw_axis( y0_, y1_, .01, .04, .1, "s", ytitle() );
  glPopMatrix();

  if(children() > 0)
    Fl_Gl_Window::draw();
}

int Fl_Plot::handle(int ev) {

  if(eventhook) {
    /* Allow clients to pre-screen our events without subclassing */
    switch((*eventhook)(this, ev)) {
    case 1: return 1;	/* pre-screener handled it */
    case -1: return 0;  /* pre-screener commands us to ignore it too */
    default: break;	/* Else just process event normally below */
    }
  }

  if(Fl_Gl_Window::handle(ev))
    return 1;

  switch(ev) {
  case FL_FOCUS:   hasfocus_ = 1; return 1;  // Yes, we want FL_KEYBOARD events
  case FL_UNFOCUS: hasfocus_ = 0; return 1;

  case FL_PUSH:
	{  // Besides navigating, grab keyboard focus if we're clicked-on
	    if(!hasfocus_) take_focus();
	}
  case FL_DRAG:
  case FL_RELEASE:
    if(pickcb_ != NULL) {

	dpickx_ = Fl::event_x();
	dpicky_ = h() - Fl::event_y();

	if(ev == FL_PUSH) {
	    do_pick( dpickx_, dpicky_ );
	    pickneeded_ = 0;
	} else {
	    if(!pickneeded_) {
		pickneeded_ = 1;
		Fl::add_idle( Fl_Plot::idlepick, (void *)this );
	    }
	}
	return 1;
    }
    /* Maybe do right-button pick? */
    return 0;

  case FL_ENTER:
    take_focus();
    return 1;

  case FL_KEYBOARD:

    if(Fl::event_text() == NULL || Fl::event_text()[0] == '\0')
	return 0;

    int c = Fl::event_text()[0];

    switch(c) {
    case 'p':
    case 'P':
		do_pick( Fl::event_x_root() - x_root(),
			 y_root() + h() - Fl::event_y_root() );
	      break;

    case '\033': exit(0);  /* ESC */

    default: c = 0;
    }

    /* Maybe check Fl::event_key(FL_HOME), etc.? */
    return c==0 ? 0 : 1;

  }
  return 0;
}


void Fl_Plot::xrange( float x0, float x1 ) { x0_ = x0; x1_ = x1; notify(); redraw(); }
void Fl_Plot::yrange( float y0, float y1 ) { y0_ = y0; y1_ = y1; notify(); redraw(); }
void Fl_Plot::xtitle( const char *str ) {
	if(xtitle_) Free(xtitle_);
	xtitle_ = str ? shmstrdup(str) : NULL;
	notify();
	redraw();
}
void Fl_Plot::ytitle( const char *str ) {
	if(ytitle_) Free(ytitle_);
	ytitle_ = str ? shmstrdup(str) : NULL;
	notify();
	redraw();
}

void Fl_Plot::picksize( float width, float height )
{
  pickwidth_ = width;
  pickheight_ = height;
}

void Fl_Plot::pickbuffer( int words, GLuint *buf )
{
  picknents_ = words;
  pickbuf_ = buf;
}
void Fl_Plot::picker( void (*pickcb)(Fl_Gl_Window *, int, int, GLuint *, void *arg), void *arg )
{
  pickcb_ = pickcb;
  pickarg_ = arg;
} 

int Fl_Plot::pickresults( int *nentp, GLuint **bufp )
{
  if(nentp) *nentp = picknents_;
  if(bufp) *bufp = pickbuf_;
  return pickhits_;
}

int Fl_Plot::do_pick( float xpick, float ypick )
{
  make_current();
  glSelectBuffer( picknents_, pickbuf_ );
  int ok = glRenderMode( GL_SELECT );
  if(ok != 0) {
    fprintf(stderr, "Trouble in do_pick: glRenderMode( GL_SELECT ) = %d\n", ok);
    ok = glRenderMode( GL_SELECT );
    fprintf(stderr, "Retry: %d\n", ok);
  }
  glPushName(0);

  inpick_ = 1;
  pickx_ = xpick;
  picky_ = ypick;
  draw();
  inpick_ = 0;

  glPopName();
  pickhits_ = glRenderMode( GL_RENDER );
  if(pickcb_)
    (*pickcb_)( this, pickhits_, picknents_, pickbuf_, pickarg_ );
  valid(0);
  return pickhits_;
}

int Fl_Plot::snapshot( int x, int y, int w, int h,  void *packedrgb )
{
  make_current();
  glPixelStorei( GL_PACK_ALIGNMENT, 1 );
  glReadBuffer( GL_FRONT );
  glReadPixels(x, y, w, h, GL_RGB, GL_UNSIGNED_BYTE, packedrgb);
  return 1; // Might return whether this window was properly uncovered?
}
    // Take snapshot into caller-supplied buffer, w*h*3 bytes long

void Fl_Plot::idlepick( void *vthis ) {
  Fl_Plot *me = (Fl_Plot *)vthis;

  if(me->pickneeded_) {
    me->do_pick( me->dpickx_, me->dpicky_ );
    me->pickneeded_ = 0;
    Fl::remove_idle( Fl_Plot::idlepick, vthis );
  }
}