/*
 * Copyright (c) 2002-2023 Julien Nadeau Carriere <vedge@csoft.net>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * Push-button widget. It can display a text label or an image.
 * It can trigger events or be connected to a boolean variable.
 */

#include <agar/core/core.h>
#ifdef AG_WIDGETS

#include <agar/gui/button.h>
#include <agar/gui/primitive.h>
#include <agar/gui/window.h>

#include <stdarg.h>

static void SetState(AG_Button *_Nonnull, AG_Variable *_Nonnull, void *_Nonnull, int);
static int  GetState(AG_Button *_Nonnull, const AG_Variable *_Nonnull, void *_Nonnull);
static int  GetStateGeneral(AG_Button *_Nonnull, const AG_Variable *_Nonnull, void *_Nonnull);
static void SetStateGeneral(AG_Button *_Nonnull, AG_Variable *_Nonnull, void *_Nonnull, int);

/* Create a new button bound to an integer (representing a boolean). */
AG_Button *
AG_ButtonNewInt(void *parent, Uint flags, const char *caption, int *v)
{
	AG_Button *bu;

	bu = AG_ButtonNewS(parent, flags, caption);
	AG_BindInt(bu, "state", v);
	return (bu);
}

/* Create a new button bound to one or more bits in a natural integer. */
AG_Button *
AG_ButtonNewFlag(void *parent, Uint flags, const char *caption,
    Uint *p, Uint bitmask)
{
	AG_Button *bu;

	bu = AG_ButtonNewS(parent, flags, caption);
	AG_BindFlag(bu, "state", p, bitmask);
	return (bu);
}

/* Create a new button with the given text label. */
AG_Button *
AG_ButtonNew(void *parent, Uint flags, const char *fmt, ...)
{
	AG_Button *bu;
	va_list ap;
	char *s;
	
	if (fmt != NULL) {
		va_start(ap, fmt);
		Vasprintf(&s, fmt, ap);
		va_end(ap);
	} else {
		s = NULL;
	}
	bu = AG_ButtonNewS(parent, flags, s);
	free(s);
	return (bu);
}

/* Create a new button and connect "button-pushed" to the given routine. */
AG_Button *
AG_ButtonNewFn(void *parent, Uint flags, const char *caption, AG_EventFn fn,
    const char *fmt, ...)
{
	AG_Button *bu;
	AG_Event *ev;
	va_list ap;

	/*
	 * Make EXCL the default behavior in this constructor since it's less
	 * common for function-calling buttons to also have a boolean binding.
	 */
	if (!(flags & AG_BUTTON_NOEXCL)) { flags |= AG_BUTTON_EXCL;  }

	bu = AG_ButtonNewS(parent, flags, caption);
	ev = AG_SetEvent(bu, "button-pushed", fn, NULL);

	va_start(ap, fmt);
	AG_EventGetArgs(ev, fmt, ap);
	va_end(ap);

	return (bu);
}

static __inline__ void
SetFocusable(AG_Button *_Nonnull bu, int focusable)
{
	if (focusable) {
		WIDGET(bu)->flags |= AG_WIDGET_FOCUSABLE;
		WIDGET(bu)->flags &= ~(AG_WIDGET_UNFOCUSED_BUTTONUP);
	} else {
		WIDGET(bu)->flags &= ~(AG_WIDGET_FOCUSABLE);
		WIDGET(bu)->flags |= AG_WIDGET_UNFOCUSED_BUTTONUP;
	}
}

/* Create a new button with the given text label. */
AG_Button *
AG_ButtonNewS(void *parent, Uint flags, const char *label)
{
	AG_Button *bu;
	
	bu = Malloc(sizeof(AG_Button));
	AG_ObjectInit(bu, &agButtonClass);

	if (label != NULL) {
		bu->label = TryStrdup(label);
	}
	bu->flags |= flags;

	if (flags & AG_BUTTON_HFILL) { WIDGET(bu)->flags |= AG_WIDGET_HFILL; }
	if (flags & AG_BUTTON_VFILL) { WIDGET(bu)->flags |= AG_WIDGET_VFILL; }
	if (flags & AG_BUTTON_NO_FOCUS) { SetFocusable(bu, 0); }
	if (flags & AG_BUTTON_REPEAT) { AG_ButtonSetRepeatMode(bu, 1); }

	if      (flags & AG_BUTTON_ALIGN_LEFT)    { bu->justify = AG_TEXT_LEFT; }
	else if (flags & AG_BUTTON_ALIGN_RIGHT)   { bu->justify = AG_TEXT_RIGHT; }
	if      (flags & AG_BUTTON_VALIGN_TOP)    { bu->valign = AG_TEXT_TOP; }
	else if (flags & AG_BUTTON_VALIGN_BOTTOM) { bu->valign = AG_TEXT_BOTTOM; }

	AG_ObjectAttach(parent, bu);
	return (bu);
}

void
AG_ButtonSetFocusable(AG_Button *bu, int enable)
{
	AG_OBJECT_ISA(bu, "AG_Widget:AG_Button:*");
	AG_ObjectLock(bu);

	SetFocusable(bu, enable);

	AG_ObjectUnlock(bu);
}

void
AG_ButtonSetSticky(AG_Button *bu, int flag)
{
	AG_OBJECT_ISA(bu, "AG_Widget:AG_Button:*");
	AG_ObjectLock(bu);

	AG_SETFLAGS(bu->flags, AG_BUTTON_STICKY, flag);

	AG_ObjectUnlock(bu);
}

void
AG_ButtonSetInverted(AG_Button *bu, int flag)
{
	AG_OBJECT_ISA(bu, "AG_Widget:AG_Button:*");
	AG_ObjectLock(bu);

	AG_SETFLAGS(bu->flags, AG_BUTTON_INVERTED, flag);

	AG_ObjectUnlock(bu);
}


/* Delay/repeat timer callbacks for REPEAT option. */
static Uint32
MouseSpinRepeat(AG_Timer *_Nonnull to, AG_Event *_Nonnull event)
{
	AG_Button *bu = AG_BUTTON_SELF();

	AG_PostEvent(bu, "button-pushed", "%i", 1);
	return (to->ival);
}
static Uint32
MouseSpinDelay(AG_Timer *_Nonnull to, AG_Event *_Nonnull event)
{
	AG_Button *bu = AG_BUTTON_SELF();

	if (bu->repeat != NULL) {
		AG_AddTimer(bu, &bu->repeat->ivalTo, agMouseSpinIval,
		    MouseSpinRepeat, NULL);
	}
	return (0);
}

static void
MouseButtonDown(void *obj, AG_MouseButton button, int x, int y)
{
	AG_Button *bu = obj;
	AG_Variable *V;
	void *pState;
	int newState;
	
	if (AG_WidgetDisabled(bu))
		return;

	if (!AG_WidgetIsFocused(bu))
		AG_WidgetFocus(bu);

	if (button != AG_MOUSE_LEFT)
		return;

	bu->flags |= AG_BUTTON_PRESSING;

	V = AG_GetVariable(bu, "state", &pState);
	if (bu->flags & AG_BUTTON_STICKY) {
		newState = !GetState(bu, V, pState);
		SetState(bu, V, pState, newState);
		AG_PostEvent(bu, "button-pushed", "%i", newState);
	} else {
		SetState(bu, V, pState, 1);
	}
	AG_UnlockVariable(V);

	if (bu->repeat) {
		AG_DelTimer(bu, &bu->repeat->ivalTo);
		AG_AddTimer(bu, &bu->repeat->delayTo, agMouseSpinDelay,
		    MouseSpinDelay, NULL);
	}
}

static void
MouseButtonUp(void *obj, AG_MouseButton button, int x, int y)
{
	AG_Button *bu = obj;
	void *pState;

	if (button != AG_MOUSE_LEFT)
		return;

	bu->flags &= ~(AG_BUTTON_PRESSING);

	if (bu->repeat) {
		AG_DelTimer(bu, &bu->repeat->ivalTo);
		AG_DelTimer(bu, &bu->repeat->delayTo);
	}
	if (AG_WidgetDisabled(bu))
		return;

	if ((bu->flags & AG_BUTTON_STICKY) == 0) {
		AG_Variable *V;

		V = AG_GetVariable(bu, "state", &pState);
		if (GetState(bu, V, pState)) {
		    	SetState(bu, V, pState, 0);
			AG_PostEvent(bu, "button-pushed", "%i", 0);
		}
		AG_UnlockVariable(V);
	}
}


static void
KeyUp(void *obj, AG_KeySym ks, AG_KeyMod km, AG_Char ch)
{
	AG_Button *bu = obj;
	AG_Variable *V;
	void *pState;
	
	if (AG_WidgetDisabled(bu))
		return;

	if (bu->repeat) {
		AG_DelTimer(bu, &bu->repeat->delayTo);
		AG_DelTimer(bu, &bu->repeat->ivalTo);
	}
	if (ks != AG_KEY_RETURN &&		/* TODO AG_Action */
	    ks != AG_KEY_KP_ENTER &&
	    ks != AG_KEY_SPACE) {
		return;
	}
	if ((bu->flags & AG_BUTTON_STICKY) == 0) {
		V = AG_GetVariable(bu, "state", &pState);
		SetState(bu, V, pState, 0);
		AG_UnlockVariable(V);
	}

	if (bu->flags & AG_BUTTON_KEYDOWN) {
		bu->flags &= ~(AG_BUTTON_KEYDOWN);
		AG_PostEvent(bu, "button-pushed", "%i", 0);
	}
}

static void
KeyDown(void *obj, AG_KeySym ks, AG_KeyMod km, AG_Char ch)
{
	AG_Button *bu = obj;
	AG_Variable *V;
	void *pState;
	
	if (AG_WidgetDisabled(bu)) {
		return;
	}
	if (ks != AG_KEY_RETURN &&		/* TODO AG_Action */
	    ks != AG_KEY_KP_ENTER &&
	    ks != AG_KEY_SPACE) {
		return;
	}
	
	V = AG_GetVariable(bu, "state", &pState);
	if (bu->flags & AG_BUTTON_STICKY) {
		const int newState = !GetState(bu, V, pState);
		SetState(bu, V, pState, newState);
		AG_PostEvent(bu, "button-pushed", "%i", newState);
	} else {
		V = AG_GetVariable(bu, "state", &pState);
		SetState(bu, V, pState, 1);
		AG_PostEvent(bu, "button-pushed", "%i", 1);
	}
	AG_UnlockVariable(V);

	bu->flags |= AG_BUTTON_KEYDOWN;
	if (bu->repeat) {
		AG_DelTimer(bu, &bu->repeat->ivalTo);
		AG_AddTimer(bu, &bu->repeat->delayTo, agMouseSpinDelay,
		    MouseSpinDelay, NULL);
	}
}

static void
Ctrl(void *obj, void *inputDevice, const AG_DriverEvent *dev)
{
	AG_Button *bu = obj;
	AG_Variable *V;
	void *pState;

	if (AG_WidgetDisabled(bu))
		return;

	if (dev->type == AG_DRIVER_CTRL_BUTTON_DOWN &&
	    dev->ctrlButton.which == AG_CTRL_BUTTON_A)
	{
		AG_GrabInputDevice(bu, inputDevice);
		bu->flags |= AG_BUTTON_PRESSING;

		V = AG_GetVariable(bu, "state", &pState);
		if (bu->flags & AG_BUTTON_STICKY) {
			const int newState = !GetState(bu,V,pState);
			SetState(bu, V, pState, newState);
			AG_PostEvent(bu, "button-pushed", "%i", newState);
		} else {
			SetState(bu, V, pState, 1);
		}
		AG_UnlockVariable(V);

		if (bu->repeat) {
			AG_DelTimer(bu, &bu->repeat->ivalTo);
			AG_AddTimer(bu, &bu->repeat->delayTo,
			    agMouseSpinDelay,
			    MouseSpinDelay,NULL);
		}
	} else if (dev->type == AG_DRIVER_CTRL_BUTTON_UP &&
	           dev->ctrlButton.which == AG_CTRL_BUTTON_A)
	{
		AG_UngrabInputDevice(bu, inputDevice);
		bu->flags &= ~(AG_BUTTON_PRESSING);

		if (bu->repeat) {
			AG_DelTimer(bu, &bu->repeat->ivalTo);
			AG_DelTimer(bu, &bu->repeat->delayTo);
		}
		if ((bu->flags & AG_BUTTON_STICKY) == 0) {
			AG_Variable *V;

			V = AG_GetVariable(bu, "state", &pState);
			if (GetState(bu, V, pState)) {
			    	SetState(bu, V, pState, 0);
				AG_PostEvent(bu, "button-pushed", "%i", 0);
			}
			AG_UnlockVariable(V);
		}
	}
}

static void
OnShow(AG_Event *_Nonnull event)
{
	AG_Button *bu = AG_BUTTON_SELF();

	if (bu->flags & AG_BUTTON_SET) {
		AG_Variable *V;
		void *pState;

		V = AG_GetVariable(bu, "state", &pState);
		SetState(bu, V, pState, 1);
		AG_UnlockVariable(V);
	}
	if ((bu->flags & AG_BUTTON_EXCL) == 0)
		AG_RedrawOnChange(bu, 250, "state");
}

static void
StyleChanged(AG_Event *_Nonnull event)
{
	AG_Button *bu = AG_BUTTON_SELF();

	if (bu->surfaceLbl != -1) {
		AG_WidgetUnmapSurface(bu, bu->surfaceLbl);
		bu->surfaceLbl = -1;
	}
}

static void
Init(void *_Nonnull obj)
{
	AG_Button *bu = obj;

	WIDGET(bu)->flags |= AG_WIDGET_FOCUSABLE |
			     AG_WIDGET_USE_TEXT |
			     AG_WIDGET_USE_MOUSEOVER;

	bu->flags = 0;
	bu->label = NULL;
	bu->state = 0;
	bu->surfaceLbl = -1;
	bu->surfaceSrc = -1;
	bu->justify = AG_TEXT_CENTER;
	bu->valign = AG_TEXT_MIDDLE;
	bu->repeat = NULL;
	bu->wReq = 0;
	bu->hReq = 0;

	AG_AddEvent(bu, "widget-shown", OnShow, NULL);
	AG_AddEvent(bu, "font-changed", StyleChanged, NULL);
	AG_AddEvent(bu, "palette-changed", StyleChanged, NULL);

	AG_BindInt(bu, "state", &bu->state);
}

static void
Destroy(void *_Nonnull obj)
{
	AG_Button *bu = obj;

	Free(bu->label);
	Free(bu->repeat);
}

static void
SizeRequest(void *_Nonnull obj, AG_SizeReq *_Nonnull r)
{
	AG_Button *bu = obj;
	const AG_Surface *S;
	
	r->w = WIDGET(bu)->paddingLeft + WIDGET(bu)->paddingRight;
	r->h = WIDGET(bu)->paddingTop + WIDGET(bu)->paddingBottom;

	if (bu->label && bu->label[0] != '\0') {
		if (bu->surfaceLbl != -1) {
			S = WSURFACE(bu, bu->surfaceLbl);
			r->w += S->w;
			r->h += S->h;
		} else {
			int wLbl, hLbl;

			AG_TextSize(bu->label, &wLbl, &hLbl);
			r->w += wLbl;
			r->h += hLbl;
		}
	} else if (bu->surfaceSrc != -1) {
		S = WSURFACE(bu, bu->surfaceSrc);
		r->w += S->w;
		r->h += S->h;
	}

	bu->wReq = r->w;
	bu->hReq = r->h;
}

static int
SizeAllocate(void *_Nonnull obj, const AG_SizeAlloc *_Nonnull a)
{
	if (a->w < 4 || a->h < 4) {
		return (-1);
	}
	return (0);
}

static void
SetState(AG_Button *bu, AG_Variable *V, void *p, int v)
{
	switch (V->type) {
	case AG_VARIABLE_INT:
	case AG_VARIABLE_P_INT:
		*(int *)p = v;
		break;
	case AG_VARIABLE_UINT:
	case AG_VARIABLE_P_UINT:
		*(Uint *)p = v;
		break;
	case AG_VARIABLE_P_FLAG:
		AG_SETFLAGS(*(Uint *)p, V->info.bitmask.u, v);
		break;
	default:
		SetStateGeneral(bu, V, p, v);
		break;
	}
	AG_Redraw(bu);
}

static void
Draw(void *_Nonnull p)
{
	AG_Button *bu = p;
	AG_Variable *V;
	void *pState;
	AG_Surface *S;
	int surface, pressed, x, y;

	V = AG_GetVariable(bu, "state", &pState);
	pressed = GetState(bu, V, pState);
	AG_UnlockVariable(V);

	if (pressed) {
		AG_DrawBoxSunk(bu, &WIDGET(bu)->r, &WCOLOR(bu, FG_COLOR));
	} else {
		AG_DrawBoxRaised(bu, &WIDGET(bu)->r, &WCOLOR(bu, FG_COLOR));
	}

	if (bu->surfaceSrc != -1) {
		surface = bu->surfaceSrc;
	} else {                           
		if (bu->surfaceLbl == -1) {
			if (bu->label == NULL || bu->label[0] == '\0') {
				/* Nothing to draw */
				return;
			}
			AG_TextColor(&WCOLOR(bu, TEXT_COLOR));
		/*	AG_TextBGColor(&WCOLOR(bu, FG_COLOR)); */
			bu->surfaceLbl = AG_WidgetMapSurface(bu,
			    (bu->flags & AG_BUTTON_CROP) ?
			    AG_TextRenderCropped(bu->label) :
			    AG_TextRender(bu->label));
		}
		surface = bu->surfaceLbl;
	}

	S = WSURFACE(bu,surface);

	switch (bu->justify) {
	case AG_TEXT_CENTER:
	default:
		x = ((WIDTH(bu) - 2) >> 1) - (S->w >> 1);
		break;
	case AG_TEXT_LEFT:
		x = WIDGET(bu)->paddingLeft;
		break;
	case AG_TEXT_RIGHT:
		x = WIDTH(bu) - S->w - WIDGET(bu)->paddingRight - 1;
		break;
	}

	switch (bu->valign) {
	case AG_TEXT_MIDDLE:
	default:
		y = (HEIGHT(bu) >> 1) - (S->h >> 1);
		break;
	case AG_TEXT_TOP:
		y = WIDGET(bu)->paddingTop;
		break;
	case AG_TEXT_BOTTOM:
		y = HEIGHT(bu) - S->h - WIDGET(bu)->paddingBottom - 1;
		break;
	}

	if (pressed) {
		x++;
		y++;
	}

	if (AG_WidgetIsFocused(bu)) {
		AG_DrawLineH(bu, 1, WIDTH(bu) - 3 + pressed,     /* x1,x2,y */
		                   HEIGHT(bu) - 3 + pressed,
		             &WCOLOR(bu, SELECTION_COLOR));

		AG_DrawLineV(bu, WIDTH(bu) - 3 + pressed,        /* x,y1,y2 */
		             1, HEIGHT(bu) - 3 + pressed,
		             &WCOLOR(bu, SELECTION_COLOR));
	}

	/* TODO Opaque label optimizations */
	AG_PushBlendingMode(bu, AG_ALPHA_SRC, AG_ALPHA_ONE_MINUS_SRC);

	if (WIDTH(bu) < bu->wReq || HEIGHT(bu) < bu->hReq) {   /* Undersize */
		AG_PushClipRectInner(bu, &WIDGET(bu)->r);
		AG_WidgetBlitSurface(bu, surface, x,y);
		AG_PopClipRect(bu);
	} else {
		AG_WidgetBlitSurface(bu, surface, x,y);
	}

	AG_PopBlendingMode(bu);
}

/* Return the current boolean state of the button. */
int
AG_ButtonGetState(AG_Button *bu)
{
	AG_Variable *V;
	void *pState;
	int stateCur;

	V = AG_GetVariable(bu, "state", &pState);
	stateCur = GetState(bu, V, pState);
	AG_UnlockVariable(V);
	return (stateCur);
}

/* Set the boolean state of the button and return the previous state. */
int
AG_ButtonSetState(AG_Button *bu, int stateNew)
{
	AG_Variable *V;
	void *pState;
	int statePrev;

	V = AG_GetVariable(bu, "state", &pState);
	statePrev = GetState(bu, V, pState);
	SetState(bu, V, pState, 1);
	AG_UnlockVariable(V);
	return (statePrev);
}

/* Atomically toggle the boolean state of the button and return the new state. */
int
AG_ButtonToggle(AG_Button *bu)
{
	AG_Variable *V;
	void *pState;
	int statePrev, stateNew;

	V = AG_GetVariable(bu, "state", &pState);
	statePrev = GetState(bu, V, pState);
	stateNew = !statePrev;
	SetState(bu, V, pState, stateNew);
	AG_UnlockVariable(V);
	return (stateNew);
}

static int
GetState(AG_Button *bu, const AG_Variable *V, void *p)
{
	int v;

	switch (V->type) {
	case AG_VARIABLE_INT:
	case AG_VARIABLE_P_INT:
	case AG_VARIABLE_UINT:
	case AG_VARIABLE_P_UINT:
		v = (*(Uint *)p) ? 1 : 0;
		break;
	case AG_VARIABLE_P_FLAG:
		v = (*(Uint *)p & V->info.bitmask.u) ? 1 : 0;
		break;
	default:
		v = GetStateGeneral(bu, V, p) ? 1 : 0;
		break;
	}
	if (bu->flags & AG_BUTTON_INVERTED) {
		v = !v;
	}
	return (v);
}

static int
GetStateGeneral(AG_Button *bu, const AG_Variable *V, void *p)
{
	switch (V->type) {
	case AG_VARIABLE_UINT8:
		return (int)(*(Uint8 *)p);
	case AG_VARIABLE_UINT16:
		return (int)(*(Uint16 *)p);
	case AG_VARIABLE_UINT32:
		return (int)(*(Uint32 *)p);
	case AG_VARIABLE_P_FLAG8:
		return (int)(*(Uint8 *)p & V->info.bitmask.u8);
	case AG_VARIABLE_P_FLAG16:
		return (int)(*(Uint16 *)p & V->info.bitmask.u16);
	case AG_VARIABLE_P_FLAG32:
		return (int)(*(Uint32 *)p & V->info.bitmask.u32);
	default:
		break;
	}
	return (0);
}

static void
SetStateGeneral(AG_Button *bu, AG_Variable *V, void *p, int v)
{
	switch (V->type) {
	case AG_VARIABLE_UINT8:
		*(Uint8 *)p = v;
		break;
	case AG_VARIABLE_UINT16:
		*(Uint16 *)p = v;
		break;
	case AG_VARIABLE_UINT32:
		*(Uint32 *)p = v;
		break;
	case AG_VARIABLE_P_FLAG8:
		AG_SETFLAGS(*(Uint8 *)p, V->info.bitmask.u8, v);
		break;
	case AG_VARIABLE_P_FLAG16:
		AG_SETFLAGS(*(Uint16 *)p, V->info.bitmask.u16, v);
		break;
	case AG_VARIABLE_P_FLAG32:
		AG_SETFLAGS(*(Uint32 *)p, V->info.bitmask.u32, v);
		break;
	default:
		break;
	}
}

/* Set horizontal alignment mode. */
void
AG_ButtonJustify(AG_Button *bu, enum ag_text_justify jus)
{
	AG_OBJECT_ISA(bu, "AG_Widget:AG_Button:*");
	bu->justify = jus;
}

/* Set vertical alignment mode. */
void
AG_ButtonValign(AG_Button *bu, enum ag_text_valign va)
{
	AG_OBJECT_ISA(bu, "AG_Widget:AG_Button:*");
	bu->valign = va;
}

/*
 * Set the button label to (a copy of) the given graphics surface.
 */
void
AG_ButtonSurface(AG_Button *bu, const AG_Surface *S)
{
	AG_Surface *Sdup = (S != NULL) ? AG_SurfaceDup(S) : NULL;

	AG_OBJECT_ISA(bu, "AG_Widget:AG_Button:*");
	AG_ObjectLock(bu);

	if (bu->surfaceSrc != -1) {
		AG_WidgetReplaceSurface(bu, bu->surfaceSrc, Sdup);
	} else {
		bu->surfaceSrc = AG_WidgetMapSurface(bu, Sdup);
	}

	AG_Redraw(bu);
	AG_ObjectUnlock(bu);
}

/*
 * Set the button label to the given graphics surface (potentially unsafe).
 */
void
AG_ButtonSurfaceNODUP(AG_Button *bu, AG_Surface *S)
{
	AG_OBJECT_ISA(bu, "AG_Widget:AG_Button:*");
	AG_ObjectLock(bu);

	if (bu->surfaceSrc != -1) {
		AG_WidgetReplaceSurfaceNODUP(bu, bu->surfaceSrc, S);
	} else {
		bu->surfaceSrc = AG_WidgetMapSurfaceNODUP(bu, S);
	}

	AG_Redraw(bu);
	AG_ObjectUnlock(bu);
}

/* Enable or Disable repeat mode */
void
AG_ButtonSetRepeatMode(AG_Button *bu, int enable)
{
	AG_OBJECT_ISA(bu, "AG_Widget:AG_Button:*");
	AG_ObjectLock(bu);

	if (enable) {
		if (bu->repeat == NULL) {
			bu->repeat = Malloc(sizeof(AG_ButtonRepeat));
			AG_InitTimer(&bu->repeat->delayTo, "delay", 0);
			AG_InitTimer(&bu->repeat->ivalTo, "ival", 0);
		}
		bu->flags |= AG_BUTTON_REPEAT;
	} else {
		if (bu->repeat != NULL) {
			AG_DelTimer(bu, &bu->repeat->ivalTo);
			AG_DelTimer(bu, &bu->repeat->delayTo);
			free(bu->repeat);
			bu->repeat = NULL;
		}
		bu->flags &= ~(AG_BUTTON_REPEAT);
	}

	AG_ObjectUnlock(bu);
}

/* Set the label text (format string). */
void
AG_ButtonText(AG_Button *bu, const char *fmt, ...)
{
	char *s;
	va_list ap;

	va_start(ap, fmt);
	Vasprintf(&s, fmt, ap);
	va_end(ap);

	AG_ButtonTextS(bu, s);

	free(s);
}

/* Set the label text (C string). */
void
AG_ButtonTextS(AG_Button *bu, const char *label)
{
	char *labelDup = TryStrdup(label);

	AG_OBJECT_ISA(bu, "AG_Widget:AG_Button:*");
	AG_ObjectLock(bu);

	if (bu->surfaceLbl != -1) {
		AG_WidgetUnmapSurface(bu, bu->surfaceLbl);
		bu->surfaceLbl = -1;
	}
	Free(bu->label);
	bu->label = labelDup;
	
	AG_Redraw(bu);
	AG_ObjectUnlock(bu);
}

AG_WidgetClass agButtonClass = {
	{
		"Agar(Widget:Button)",
		sizeof(AG_Button),
		{ 1,0, AGC_BUTTON, 0xE012 },
		Init,
		NULL,		/* reset */
		Destroy,
		NULL,		/* load */
		NULL,		/* save */
		NULL		/* edit */
	},
	Draw,
	SizeRequest,
	SizeAllocate,
	MouseButtonDown,
	MouseButtonUp,
	NULL,			/* mouse_motion */
	KeyDown,
	KeyUp,
	NULL,			/* touch */
	Ctrl,
	NULL			/* joy */
};

#endif /* AG_WIDGETS */
