#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <memory.h>
#include <string.h>

#include <ctype.h>
#undef isspace

#include "CMedit.H"

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

int CMedit::fload( FILE *inf ) {
  char line[256];
  char *cp;
  int count = -1;
  int ix, ox, nix, prevox;
  float rgba[4], hsba[4], phsba[4];
  int lno;
  static enum CMfield flds[4] = { HUE, SAT, BRIGHT, ALPHA };
  int f;
  char tc[2];

  lno = 0;
  while(fgets(line, sizeof(line), inf) != NULL) {
    lno++;
    for(cp = line; *cp && isspace(*cp); cp++)
	;
    if(*cp == '\0' || *cp == '\n')
	continue;

    if(*cp == '#') {
	if(ncomments >= maxcomments) {
	    maxcomments *= 2;
	    comments = (char **)realloc( comments, maxcomments * sizeof(char *) );
	}
	comments[ncomments++] = strdup( line );
	continue;
    }

    /* ``nnn:'' entries set the colormap pointer ... */
    if(sscanf(line, "%d%1[:]", &nix, tc) == 2) {
	ix = nix;
	cp = strchr(line, ':') + 1;
	while(*cp && isspace(*cp)) cp++;
	if(*cp == '\0' || *cp == '\n' || *cp == '#')
	    continue;
    }

    if(count == -1) {
	if(!sscanf(line, "%d", &count) || count < 1) {
	    fprintf(stderr, "Not a .cmap file?  Doesn't begin with a number.\n");
	    return 0;
	}
	cment( count );
	ix = 0, prevox = 0;
	continue;
    } else {

	if(ix >= count)
	    break;
	    
	rgba[3] = 1;
	if(sscanf(cp, "%f%f%f%f", &rgba[0],&rgba[1],&rgba[2],&rgba[3]) < 3) {
	    fprintf(stderr, "Couldn't read colormap line %d (cmap entry %d of 0..%d)\n",
		lno, ix, count-1);
	    return 0;
	}
	rgb2hsb( rgba[0],rgba[1],rgba[2], &hsba[0],&hsba[1],&hsba[2] );
	hsba[3] = rgba[3];
	if(ox == 0)
	    memcpy(phsba, hsba, sizeof(phsba));
	else
	    phsba[0] = huenear(phsba[0], hsba[0]);
	ox = (cment_-1) * ix / count + 1;
	for(f = 0; f < 4; f++) {
	    dragrange( prevox, ox, flds[f], phsba[f], hsba[f], 1.0 );
	    phsba[f] = hsba[f];
	}
	prevox = ox;
	ix++;
    }

  }
  if(count <= 0) {
    fprintf(stderr, "Empty colormap file?\n");
    return 0;
  }
  if(ix < count)
    fprintf(stderr, "Only got %d colormap entries, expected %d\n", ix, count);

  for(f = 0; f < 4; f++)
    dragrange( prevox, cment_, flds[f], phsba[f], phsba[f], 1.0 );
  return ix > 0;
}

int CMedit::fsave( FILE *outf ) {
  float r,g,b;
  int i, ok = 1;
  for(i = 0; i < ncomments; i++)
    fputs(comments[i], outf);
  if(ncomments > 0)
    fputs("\n", outf);

  fprintf(outf, "%d\n", cment_);
  for(i = 0; i < cment_; i++) {
    hsb2rgb( vh[i], vs[i], vb[i], &r, &g, &b );
    if(fprintf(outf, "%f %f %f %f\n", r, g, b, alpha[i]) <= 0)
	ok = 0;
  }
  return ok;
}
    
#define  YMAX  (1.0)
#define  YMIN  (-.25)
#define  YBAR0  (-.01)
#define  YBAR1  (-.06)

#define  XMIN  (-cment_ / 16.f)
#define  XMAX  cment_

int CMedit::wx2x( int wx ) {
  return XMIN + wx * (XMAX-XMIN) / w();
}

float CMedit::wy2y( int wy ) {
  return YMAX - wy * (YMAX-YMIN) / h();
}

void CMedit::draw() {
  int i;
  float v;
  int coarse = (w() >= 2*cment());

  if(!valid() || damage()/* == FL_DAMAGE_ALL*/) {
    /* Assume reshaped */
    valid(1);
    remin = 0;  remax = cment()-1;
    glViewport( 0, 0, w(), h() );

    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    glOrtho( XMIN, XMAX,  YMIN, 1,  -1, 1 );
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();
  }

  if(remax-remin < cment()-1) {
    glEnable( GL_SCISSOR_TEST );
    glScissor( remin*w()/cment(), (remax+1)*w()/cment(), 0, h() );
  } else {
    glDisable( GL_SCISSOR_TEST );
  }

  glClearColor( 0,0,0,0 );
  glClear( GL_COLOR_BUFFER_BIT );

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

  glBegin( GL_QUADS );
  glColor3f( 0,0,0 );
  glVertex2f( remin, -.05 );
  glVertex2f( remax+1, -.05 );
  glColor3f( 1,1,1 );
  glVertex2f( remax+1, YMIN );
  glVertex2f( remin, YMIN );
  glEnd();

  glLineWidth( 1 );
  glDisable( GL_BLEND );

  if(hsbmode) {
    glColor3f( 1,1,0 ); /* Hue: yellow */
    float midhue = y2hue(.5);
    glBegin( GL_LINE_STRIP );
    for(i = remin; i <= remax; i++) {
	glVertex2f( i, v = hue2y( huenear( vh[i], midhue ) ) );
	if(coarse) glVertex2f( i+1, v );
    }
    glEnd();

    glColor3f( .3,1,.25 );  /* Saturation: teal */
    glBegin( GL_LINE_STRIP );
    for(i = remin; i <= remax; i++) {
	glVertex2f( i, vs[i] );
	if(coarse) glVertex2f( i+1, vs[i] );
    }
    glEnd();

    glColor3f( .5,.2,1 );  /* Brightness: purple */
    glBegin( GL_LINE_STRIP );
    for(i = remin; i <= remax; i++) {
	glVertex2f( i, vb[i] );
	if(coarse) glVertex2f( i+1, vb[i] );
    }
    glEnd();
  } else {

    float r[CMENTMAX], g[CMENTMAX], b[CMENTMAX];

    for(i = remin; i <= remax; i++)
	hsb2rgb( vh[i], vs[i], vb[i], &r[i], &g[i], &b[i] );

    glColor3f( 1,0,0 ); /* red */
    glBegin( GL_LINE_STRIP );
    for(i = remin; i <= remax; i++) {
	glVertex2f( i, r[i] );
	if(coarse) glVertex2f( i+1, r[i] );
    }
    glEnd();

    glColor3f( 0,1,0 );  /* green */
    glBegin( GL_LINE_STRIP );
    for(i = remin; i <= remax; i++) {
	glVertex2f( i, g[i] );
	if(coarse) glVertex2f( i+1, g[i] );
    }
    glEnd();

    glColor3f( 0,0,1 );  /* blue */
    glBegin( GL_LINE_STRIP );
    for(i = remin; i <= remax; i++) {
	glVertex2f( i, b[i] );
	if(coarse) glVertex2f( i+1, b[i] );
    }
    glEnd();
  }   

  glColor3f( .7,.7,.7 );  /* alpha: gray */
  glBegin( GL_LINE_STRIP );
  for(i = remin; i <= remax-coarse; i++) {
    glVertex2f( i, alpha[i] );
    if(coarse) glVertex2f( i+1, alpha[i] );
  }
  glEnd();

  glDisable( GL_BLEND );
  glShadeModel( GL_SMOOTH );
  glBegin( GL_QUAD_STRIP );
  for(i = remin; i <= remax; i++) {
    float rgb[3];
    hsb2rgb( vh[i], vs[i], vb[i], &rgb[0],&rgb[1],&rgb[2] );
    glColor3fv( rgb );
    glVertex2f( i, YBAR0 );
    glVertex2f( i, YBAR1 );
    if(coarse) {
	glVertex2f( i+1, YBAR0 );
	glVertex2f( i+1, YBAR1 );
    }
  }
  glEnd();

  glEnable( GL_BLEND );
  glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
  glShadeModel( GL_SMOOTH );
  glBegin( GL_QUAD_STRIP );
  for(i = remin; i <= remax; i++) {
    float rgba[4];
    hsb2rgb( vh[i], vs[i], vb[i], &rgba[0],&rgba[1],&rgba[2] );
    rgba[3] = alpha[i];
    glColor4fv( rgba );
    glVertex2f( i, YBAR1 );
    glVertex2f( i, YMIN );
    if(coarse) {
	glVertex2f( i+1, YBAR1 );
	glVertex2f( i+1, YMIN );
    }
  }
  glEnd();
  glDisable( GL_BLEND );

  if(remin <= 0) {
    glBegin( GL_QUAD_STRIP );
    for(i = 0; i < 128; i++) {
	float rgb[3];
	float y = i / 127.;
	hsb2rgb( y2hue(y), 1, 1, &rgb[0],&rgb[1],&rgb[2] );
	glColor3fv( rgb );
	glVertex2f( XMIN, y );
	glVertex2f( XMIN/4, y );
    }
    glEnd();
  }

  glFinish();

  /* draw (I hope) any children lying on top of us */
  if(children()) Fl_Gl_Window::draw();
}


float CMedit::drag( int x, enum CMfield field, float y, float lerp ) {
  float ny, oy, *vp;
  float rgb[3];
  int usergb = 0;
  if(x < 0 || x >= cment())
    return 0;
  if(locked && (x < lockmin || x > lockmax))
    return 0;
  switch(field) {
  case HUE: vp = &vh[x]; break;
  case SAT: vp = &vs[x]; break;
  case BRIGHT: vp = &vb[x]; break;
  case ALPHA: vp = &alpha[x]; break;
  default:
	hsb2rgb( vh[x],vs[x],vb[x], &rgb[0],&rgb[1],&rgb[2] );
	vp = &rgb[ field - RED ];
	usergb = 1;
  }
  if(field == HUE) {
    oy = hue2y(*vp);
    y = hue2y( huenear( y2hue(y), *vp ) );
    ny = (1-lerp) * oy + lerp * y;
    *vp = y2hue(ny);
  } else {
    oy = *vp;
    ny = (1-lerp) * oy + lerp * y;
    if(ny < 0) ny = 0;
    else if(ny > 1) ny = 1;
    *vp = ny;
    if(usergb)
	rgb2hsb( rgb[0],rgb[1],rgb[2], &vh[x],&vs[x],&vb[x] );
  }
  return ny;
}

void CMedit::dragrange( int x0, int x1, enum CMfield field, float y0, float y1, float lerp ) {
  int x;
  float dy;
  if(x0 == x1  || x0 < 0 && x1 < 0  ||  x0 >= cment() && x1 >= cment())
    return;

  dy = (y1 - y0) / (x1 - x0);
  if(x0 < x1)
    for(x = x0; x < x1; x++)
	drag( x, field, y0 + dy*(x-x0), lerp );
  else
    for(x = x0; x > x1; x--)
	drag( x, field, y0 + dy*(x-x0), lerp );
}


int CMedit::handle(int ev) {
  int x = wx2x( Fl::event_x() );
  float y = wy2y( Fl::event_y() );
  int xmin, xmax;
 
  switch(ev) {

  case FL_SHORTCUT:
    if(Fl::event_key() == 'u') {
	undo();
	return 1;
    }
    return 0;

  case FL_PUSH:
    dragfrom = x, dragval = y, dragamount = 0;
    draghue = ( (x - XMIN) * (x - (XMIN/4)) < 0 );	/* If dragging on hue strip */

    if(Fl::event_state(FL_SHIFT)) {
	dragfield = ALPHA;
    } else {
	if(hsbmode) {
	    if(Fl::event_state(FL_BUTTON1)) dragfield = HUE;
	    else if(Fl::event_state(FL_BUTTON2)) dragfield = SAT;
	    else dragfield = BRIGHT;
	} else {
	    if(Fl::event_state(FL_BUTTON1)) dragfield = RED;
	    else if(Fl::event_state(FL_BUTTON2)) dragfield = GREEN;
	    else dragfield = BLUE;
	}
    }

    if(Fl::event_key('l')) {
	/*...*/
    } else if(Fl::event_key('r')) {
	/*...*/
    } else {
	snapshot();
    }
    /* Fall into ... */
    
  case FL_DRAG:
  case FL_RELEASE:

#ifdef NOTYET
    if(draghue) {
	float h0 = y2hue( y );
	hueshift += (y - dragval) / huezoom;
	huezoom *= (x - dragfrom)
    } else { ... }
#endif
    dragrange( dragfrom, x, dragfield, dragval, y,
		    Fl::event_state(FL_CTRL) ? 1.0 : lerpval );
    xmin = (dragfrom<x) ? dragfrom : x;
    xmax = dragfrom+x-xmin;
    if(damage() == 0) {
	remin = xmin, remax = xmax;
    } else {
	if(remin > xmin) remin = xmin;
	if(remax < xmax) remax = xmax;
    }
    damage(1);
    if(dragfrom != x)
	dragamount = 1;
    if(ev == FL_RELEASE && dragamount == 0)
	drag( x, dragfield, y, Fl::event_state(FL_CTRL) ? 1.0 : lerpval );

    dragfrom = x, dragval = y;
    report( x );
    return 1;

  case FL_ENTER:
  case FL_LEAVE:
    report( x );
    return 1;

  case FL_MOVE:
    report( x );
    return 1;
    
  }
  return 0;
}

float CMedit::huenear( float hue, float hueref ) {
  float h = hue;
  if(h - hueref > .5f)
    do { h -= 1.0f; } while (h - hueref > .5);
  else
    while(h - hueref < -.5f) h += 1.0f;
  return h;
}

float CMedit::hue2y( float hue ) {
  /* Find closest multiple of given hue to reference y-position y0 */
  return (hue - hueshift) * huezoom;
}

float CMedit::y2hue( float y ) {
  return y / huezoom + hueshift;
}

static float sample( float *a, int ents, float at, int smooth ) {
  if(at <= 0 || ents <= 1) return a[0];
  if(at >= 1) return a[ents-1];
  float eat = at * ents;
  int iat = (int)eat;
  return smooth ? a[iat]*(1 - (eat-iat)) + a[iat+1]*(eat-iat)
		: a[iat];
}

void CMedit::cment( int newcment ) {
  if(newcment < 1) return;
  if(newcment > CMENTMAX) {
    fprintf(stderr, "Oops, can't ask for more than %d colormap entries -- using %d\n",
	CMENTMAX,CMENTMAX);
    newcment = CMENTMAX;
  }
  if(cment_ == newcment)
    return;

  /* Resample */

  snapshot();

  int smooth = 0; // (cment_ < newcment);
  for(int o = 0; o < newcment; o++) {
    float at = newcment > 1 ? (float)o / (newcment-1) : 0;
    vh[o] = sample( &snap[0][0], cment_, at, smooth );
    vs[o] = sample( &snap[1][0], cment_, at, smooth );
    vb[o] = sample( &snap[2][0], cment_, at, smooth );
    alpha[o] = sample( &snap[3][0], cment_, at, smooth );
  }
  cment_ = newcment;
  remin = 0;
  remax = cment_ - 1;
  lockmin = 0;
  lockmax = cment_ - 1;
  redraw();
}


void CMedit::reportto( void (*func)(int x, float h,float s,float b,float a, float red,float green,float blue) ) {
  reportfunc = func;
}

void CMedit::report( int x ) {
  float r,g,b;
  if(x < 0 || x > cment()-1 || reportfunc == NULL) 
    return;

  hsb2rgb( vh[x], vs[x], vb[x], &r,&g,&b );
  (*reportfunc)( x, vh[x], vs[x], vb[x], alpha[x], r,g,b );
}