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

#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Box.H>
#include <FL/fl_draw.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Value_Slider.H>
#include <FL/Fl_Return_Button.H>

class Fl_Marquee : public Fl_Box {

	int (*Loadfunc)(long unsigned int offset, int len, char *buf, void* data);
	// returns how much was actually read into buf from offset into the datastream
	// MUST fill up buf unless not enough data left to do so
	// data is passthru data
	void *Loadfunc_Data;
	void (*Indexchange_cb)(long unsigned int offset, void* data);
	// gets called when the index into the datastream changes
	void *Indexchange_cb_data;
	long offset;		// offset into the datastream
	int Direction;		// 1 = scroll left to right, -1 = right to left, 0 = pause;
	unsigned int Threshold;	// how close to the edges of the buffer to get before reloading
	#define FL_MARQUEE_THRESHOLD 64 /* so initialization is in one place */
	char buf[1024 + 128];	// buffer to hold loaded data
	int loaded;		// how much data is actually in the buffer
	int bufi;		// offset of the current char in buf
	int pix;		// offset into the current char
	int bufiw;		// width of the current char
	unsigned int Delay;	// msec between redraws
	int Started;		// boolean whether we're Started or not
	int Ppm;		// pixels-per-move

	void checkthreshold() {
		if (pix < 0) pix = 0; // dont move past start of char
		//if (pix >= 2* bufiw) pix = (2*bufiw) - 1; // or past end of char
		if (pix >= 50) pix = 49; // or past end of char
		if (bufi < 0) bufi = 0; // dont move past start of data
		if (bufi >= loaded) bufi = loaded - 1; // or past end of data
		if (bufi <= (int)Threshold ||
		    bufi >= (int)(sizeof(buf) - Threshold)) {
			long newoffset = offset + bufi - sizeof(buf) / 2;
//			printf("Thresholding: bufi %d, offset %ld, newoffset %ld\n", 
//				bufi, offset, newoffset);
			if (newoffset < 0) newoffset = 0;
			if (newoffset == offset) return; // no work to do
			if (newoffset > offset + loaded) return; // dont try and load past eof
			loaded = Loadfunc(newoffset, sizeof(buf), buf, Loadfunc_Data);
			offset = newoffset;
			bufi = sizeof(buf) / 2;
//			printf("...loaded %d bytes from offset %ld, bufi reset to %d\n", loaded, offset, bufi);
		}
	}	

	void moveleft(int p) {
		// pix++;
		pix += p;
		if (pix >= bufiw) {
			bufi++;
			Indexchange_cb(offset+bufi, Indexchange_cb_data);
			// pix = 0;
			pix = pix - bufiw;
			bufiw = int(fl_width(buf[bufi]));
			checkthreshold();
		}
		redraw();
	}

	void moveleft() {
		moveleft(Ppm);
	}

	void moveright(int p) {
		// pix--;
		pix -= p;
		if (pix < 0) {
			bufi--;
			Indexchange_cb(offset+bufi, Indexchange_cb_data);
			bufiw = int(fl_width(buf[bufi]));
			// pix = bufiw - 1;
			pix = bufiw - 1 + pix;
			checkthreshold();
		}
		redraw();
	}

	void moveright() {
		moveright(Ppm);
	}

	void movedirection(int direction) { // negative:positive == left:right
		if (direction > 0) {
			moveleft();
		} else if (direction <0) {
			moveright();
		}
	}

	protected:

	void draw() {
		Fl_Box::draw();
		uchar lsize = labelsize();
		fl_font(labelfont(), lsize);
//		fl_color(labelcolor());
		fl_color(FL_BLACK);
		fl_draw(&buf[bufi], x() - pix, y() + (lsize + h()) / 2);
//		fl_clip(x(), y(), w(), h());
	}

	public:

	static void timer_cb(void *data) {
		Fl_Marquee* m = (Fl_Marquee*)data;
		m->move();
		if (m->started()) {
//			Fl::add_timeout((m->delay()*1.0)/1000.0, timer_cb, data);
			Fl::repeat_timeout((m->delay()*1.0)/1000.0, timer_cb, data);
		}
	}

	Fl_Marquee(int x, int y, int w, int h, int (*lf)(unsigned long int, int, char*, void*), void* data) : Fl_Box(x, y, w, h) {
		box(FL_FLAT_BOX);
		delay(50);
		ppm(1);
		loadfunc(lf, data);
		reset();
	}

	void reset() {
		Direction = 0;
		Threshold = FL_MARQUEE_THRESHOLD;
		index(0);
	}

	void index(long unsigned int i) {
		offset = i;
		bufi = 0;
		pix = 0;
		bufiw = 0;
		loaded = Loadfunc(offset, sizeof(buf), buf, Loadfunc_Data);
		redraw();
	}

	long unsigned int index() {
		return (long unsigned int)offset;
	}

	void indexchange_cb(void (*ic)(long unsigned int, void*), void* data) {
		Indexchange_cb = ic;
		Indexchange_cb_data = data;
	}

	void loadfunc(int (*lf)(long unsigned int, int, char*, void*), void* data) {
		Loadfunc = lf;
		Loadfunc_Data = data;
	}

	int ppm() {
		return Ppm;
	}

	void ppm(int p) {
		// note that no matter how high you set this the code will never advance more than
		// character per move, so the fastest speed is really 1 character per delay ms
		Ppm = p;
	}
 
	int direction() {
		return Direction;
	}

	void direction(int d) {
		Direction = d;
	}

	void delay(int d) {
		Delay = d;
	}

	int delay() {
		return Delay;
	}
	
	void threshold(int t) {
		Threshold = t;
	}

	int threshold() {
		return Threshold;
	}
	
	void start() {
		if (!Started) {
			Started = 1;
			Fl::add_timeout((Delay*1.0)/1000.0, timer_cb, (void *)this);
		}
	}

	void stop() {
		Started = 0;
	}

	int started() {
		return Started;
	}

	void move() {
		movedirection(direction());
	}

	void labeltype(Fl_Labeltype lt) {
		Fl_Box::labeltype(lt);
	}

};
