/*
 * This file is subject to the terms of the GFX License. If a copy of
 * the license was not distributed with this file, you can obtain one at:
 *
 *              http://ugfx.org/license.html
 */

/**
 * @file    src/gwin/progressbar.c
 * @brief   GWIN sub-system progressbar code.
 *
 * @defgroup Progressbar Progressbar
 * @ingroup GWIN
 *
 * @{
 */

#include "gfx.h"

#if (GFX_USE_GWIN && GWIN_NEED_PROGRESSBAR) || defined(__DOXYGEN__)

#include "src/gwin/class_gwin.h"

// Reset the display position back to the value predicted by the saved progressbar position
static void ResetDisplayPos(GProgressbarObject *gsw) {
	if (gsw->w.g.width < gsw->w.g.height)
		gsw->dpos = gsw->w.g.height-1-((gsw->w.g.height-1)*(gsw->pos-gsw->min))/(gsw->max-gsw->min);
	else
		gsw->dpos = ((gsw->w.g.width-1)*(gsw->pos-gsw->min))/(gsw->max-gsw->min);
}

// We have to deinitialize the timer which auto updates the progressbar if any
static void _destroy(GHandle gh) {
	#if GFX_USE_GTIMER
		gtimerDeinit( &((GProgressbarObject *)gh)->gt );
	#endif

	_gwidgetDestroy(gh);
}

// The progressbar VMT table
static const gwidgetVMT progressbarVMT = {
	{
		"Progressbar",				// The classname
		sizeof(GProgressbarObject),	// The object size
		_destroy,		// The destroy routine
		_gwidgetRedraw,			// The redraw routine
		0,						// The after-clear routine
	},
	gwinProgressbarDraw_Std,			// The default drawing routine
	#if GINPUT_NEED_MOUSE
		{
			0,						// Process mouse down events (NOT USED)
			0,						// Process mouse up events
			0,						// Process mouse move events
		},
	#endif
	#if GINPUT_NEED_TOGGLE
		{
			0,						// 1 toggle role
			0,						// Assign Toggles
			0,						// Get Toggles
			0,						// Process toggle off events (NOT USED)
			0,						// Process toggle on events
		},
	#endif
	#if GINPUT_NEED_DIAL
		{
			0,						// 1 dial roles
			0,						// Assign Dials
			0,						// Get Dials
			0,						// Process dial move events
		},
	#endif
};

GHandle gwinGProgressbarCreate(GDisplay *g, GProgressbarObject *gs, const GWidgetInit *pInit) {
	if (!(gs = (GProgressbarObject *)_gwidgetCreate(g, &gs->w, pInit, &progressbarVMT)))
		return 0;

	gs->min = 0;
	gs->max = 100;
	gs->res = 1;
	gs->pos = 0;
	gs->delay = 0;

	ResetDisplayPos(gs);
	gwinSetVisible((GHandle)gs, pInit->g.show);
	
	return (GHandle)gs;
}

void gwinProgressbarSetRange(GHandle gh, int min, int max) {
	#define gsw		((GProgressbarObject *)gh)

	if (gh->vmt != (gwinVMT *)&progressbarVMT)
		return;

	if (min == max)		// prevent divide by 0 errors.
		max++;
	gsw->min = min;
	gsw->max = max;
	gsw->pos = min;

	ResetDisplayPos(gsw);

	#undef gsw
}

void gwinProgressbarSetPosition(GHandle gh, int pos) {
	#define gsw		((GProgressbarObject *)gh)

	if (gh->vmt != (gwinVMT *)&progressbarVMT)
		return;

	if (gsw->min <= gsw->max) {
		if (pos < gsw->min) gsw->pos = gsw->min;
		else if (pos > gsw->max) gsw->pos = gsw->max;
		else gsw->pos = pos;
	} else {
		if (pos > gsw->min) gsw->pos = gsw->min;
		else if (pos < gsw->max) gsw->pos = gsw->max;
		else gsw->pos = pos;
	}

	ResetDisplayPos(gsw);
	_gwidgetRedraw(gh);

	#undef gsw
}

void gwinProgressbarSetResolution(GHandle gh, int resolution) {
	#define gsw		((GProgressbarObject *)gh)

	if (gh->vmt != (gwinVMT *)&progressbarVMT)
		return;

	if (resolution <= 0)
		resolution = 1;

	gsw->res = resolution;

	#undef gsw
}

void gwinProgressbarIncrement(GHandle gh) {
	#define gsw		((GProgressbarObject *)gh)

	if (gh->vmt != (gwinVMT *)&progressbarVMT)
		return;

	if (gsw->max - gsw->pos > gsw->res)
		gsw->pos += gsw->res;
	else
		gsw->pos = gsw->max;

	ResetDisplayPos(gsw);
	_gwidgetRedraw(gh);

	#undef gsw
}

void gwinProgressbarDecrement(GHandle gh) {
	#define gsw		((GProgressbarObject *)gh)

	if (gh->vmt != (gwinVMT *)&progressbarVMT)
		return;

	if (gsw->pos > gsw->res)
		gsw->pos -= gsw->min;
	else
		gsw->pos = gsw->min;

	gsw->pos -= gsw->res;

	ResetDisplayPos(gsw);
	_gwidgetRedraw(gh);

	#undef gsw
}

// used by gwinProgressbarStart();
void _progressbarCallback(void *param) {
	#define gsw		((GProgressbarObject *)gh)
	GHandle gh = (GHandle)param;

	if (gh->vmt != (gwinVMT *)&progressbarVMT)
		return;

	gwinProgressbarIncrement(gh);

	if (gsw->pos < gsw->max)
		gtimerStart(&(gsw->gt), _progressbarCallback, gh, FALSE, gsw->delay);

	#undef gsw	
}

void gwinProgressbarStart(GHandle gh, delaytime_t delay) {
	#define gsw		((GProgressbarObject *)gh)

	if (gh->vmt != (gwinVMT *)&progressbarVMT)
		return;

	gsw->delay = delay;

	gtimerInit(&(gsw->gt));
	gtimerStart(&(gsw->gt), _progressbarCallback, gh, FALSE, gsw->delay);

	// if this is not made, the progressbar will not start when the it's already visible
	if (gsw->w.g.flags & GWIN_FLG_VISIBLE) {
		gwinSetVisible(gh, FALSE);
		gwinSetVisible(gh, TRUE);
	}

	#undef gsw
}

void gwinProgressbarStop(GHandle gh) {
	#define gsw		((GProgressbarObject *)gh)

	if (gh->vmt != (gwinVMT *)&progressbarVMT)
		return;

	gtimerStop(&(gsw->gt));

	#undef gsw
}

/*----------------------------------------------------------
 * Custom Draw Routines
 *----------------------------------------------------------*/

void gwinProgressbarDraw_Std(GWidgetObject *gw, void *param) {
	#define gsw			((GProgressbarObject *)gw)
	
	const GColorSet *	pcol;
	(void)				param;

	if (gw->g.vmt != (gwinVMT *)&progressbarVMT)
		return;

	// disable the auto-update timer if any
	#if GFX_USE_GTIMER
		if (gtimerIsActive(&(gsw->gt)) && !(gw->g.flags & GWIN_FLG_ENABLED)) {
			gtimerStop(&(gsw->gt));
		}
	#endif

	// get the colors right
	if ((gw->g.flags & GWIN_FLG_ENABLED)) 
		pcol = &gw->pstyle->pressed;
	else
		pcol = &gw->pstyle->disabled;

	// Vertical progressbar
	if (gw->g.width < gw->g.height) {
		if (gsw->dpos != gw->g.height-1)
			gdispGFillArea(gw->g.display, gw->g.x, gw->g.y+gsw->dpos, gw->g.width, gw->g.height - gsw->dpos, pcol->progress);				// Active Area
		if (gsw->dpos != 0)
			gdispGFillArea(gw->g.display, gw->g.x, gw->g.y, gw->g.width, gsw->dpos, gw->pstyle->enabled.progress);							// Inactive area
		gdispGDrawBox(gw->g.display, gw->g.x, gw->g.y, gw->g.width, gw->g.height, pcol->edge);												// Edge
		gdispGDrawLine(gw->g.display, gw->g.x, gw->g.y+gsw->dpos, gw->g.x+gw->g.width-1, gw->g.y+gsw->dpos, pcol->edge);					// Thumb

	// Horizontal progressbar
	} else {
		if (gsw->dpos != gw->g.width-1)
			gdispGFillArea(gw->g.display, gw->g.x+gsw->dpos, gw->g.y, gw->g.width-gsw->dpos, gw->g.height, gw->pstyle->enabled.progress);	// Inactive area
		if (gsw->dpos != 0)
			gdispGFillArea(gw->g.display, gw->g.x, gw->g.y, gsw->dpos, gw->g.height, pcol->progress);										// Active Area
		gdispGDrawBox(gw->g.display, gw->g.x, gw->g.y, gw->g.width, gw->g.height, pcol->edge);												// Edge
		gdispGDrawLine(gw->g.display, gw->g.x+gsw->dpos, gw->g.y, gw->g.x+gsw->dpos, gw->g.y+gw->g.height-1, pcol->edge);					// Thumb
	}
	gdispGDrawStringBox(gw->g.display, gw->g.x+1, gw->g.y+1, gw->g.width-2, gw->g.height-2, gw->text, gw->g.font, pcol->text, justifyCenter);

	#undef gsw
}

#if GDISP_NEED_IMAGE
void gwinProgressbarDraw_Image(GWidgetObject *gw, void *param) {
	#define gsw			((GProgressbarObject *)gw)
	#define gi			((gdispImage *)param)
	const GColorSet *	pcol;
	coord_t				z, v;

	if (gw->g.vmt != (gwinVMT *)&progressbarVMT)
		return;

	if ((gw->g.flags & GWIN_FLG_ENABLED))
		pcol = &gw->pstyle->pressed;
	else
		pcol = &gw->pstyle->disabled;

	if (gw->g.width < gw->g.height) {			// Vertical progressbar
		if (gsw->dpos != 0)							// The unfilled area
			gdispGFillArea(gw->g.display, gw->g.x, gw->g.y, gw->g.width, gsw->dpos, gw->pstyle->enabled.progress);	// Inactive area
		if (gsw->dpos != gw->g.height-1) {			// The filled area
			for(z=gw->g.height, v=gi->height; z > gsw->dpos;) {
				z -= v;
				if (z < gsw->dpos) {
					v -= gsw->dpos - z;
					z = gsw->dpos;
				}
				gdispGImageDraw(gw->g.display, gi, gw->g.x, gw->g.y+z, gw->g.width, v, 0, gi->height-v);
			}
		}
		gdispGDrawBox(gw->g.display, gw->g.x, gw->g.y, gw->g.width, gw->g.height, pcol->edge);								// Edge
		gdispGDrawLine(gw->g.display, gw->g.x, gw->g.y+gsw->dpos, gw->g.x+gw->g.width-1, gw->g.y+gsw->dpos, pcol->edge);	// Thumb

	// Horizontal progressbar
	} else {
		if (gsw->dpos != gw->g.width-1)				// The unfilled area
			gdispGFillArea(gw->g.display, gw->g.x+gsw->dpos, gw->g.y, gw->g.width-gsw->dpos, gw->g.height, gw->pstyle->enabled.progress);	// Inactive area
		if (gsw->dpos != 0) {						// The filled area
			for(z=0, v=gi->width; z < gsw->dpos; z += v) {
				if (z+v > gsw->dpos)
					v -= z+v - gsw->dpos;
				gdispGImageDraw(gw->g.display, gi, gw->g.x+z, gw->g.y, v, gw->g.height, 0, 0);
			}
		}
		gdispGDrawBox(gw->g.display, gw->g.x, gw->g.y, gw->g.width, gw->g.height, pcol->edge);								// Edge
		gdispGDrawLine(gw->g.display, gw->g.x+gsw->dpos, gw->g.y, gw->g.x+gsw->dpos, gw->g.y+gw->g.height-1, pcol->edge);	// Thumb
	}
	gdispGDrawStringBox(gw->g.display, gw->g.x+1, gw->g.y+1, gw->g.width-2, gw->g.height-2, gw->text, gw->g.font, pcol->text, justifyCenter);

	#undef gsw
}
#endif /* GDISP_NEED_IMAGE */

#endif /* GFX_USE_GWIN && GWIN_NEED_BUTTON */
/** @} */

