/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include <stdio.h>
#include <math.h>

#include  <X11/Intrinsic.h>
#include  <X11/IntrinsicP.h>
#include  <X11/StringDefs.h>
#include  <X11/CoreP.h>
#include  <X11/CompositeP.h>
#include  <X11/ConstrainP.h>
#include  <Xm/XmP.h>
#include  <Xm/DrawingAP.h>
#include  <Xm/ArrowBP.h>
#include  <Xm/SeparatorP.h>
#include  "Carousel.h"
#include  "CarouselP.h"

#ifndef MAX
#define   MAX(a,b) ((a) > (b) ? (a) : (b))
#endif
#ifndef MIN
#define   MIN(a,b) ((a) < (b) ? (a) : (b))
#endif

static void             Initialize();
static void             Resize();
static void             Destroy();
static Boolean          SetValues();
static XtGeometryResult GeometryManager();
static void             ChangeManaged();
static void             new_layout();
static void 			left();
static void 			right();
static void 			scroll();
static int 				get_nb_children();
static int 				get_column_width();
static void 			show_arrows();

static void             InsertChild();
static void             DeleteChild();
static int				widget_is_set();
static void				widget_reset();
static void				force_radio_behaviour();
static void             toggle_action();

#define DEBUG(a) printf("%s\n",a);

static XtResource resources[] = {
	{XtNspeed,XtCSpeed, XmRInt,sizeof(int),
	XtOffset(CarouselWidget,carousel.speed), XtRImmediate,(XtPointer) 5 },

	{XmNcolumns,XmCColumns, XmRInt,sizeof(int),
	XtOffset(CarouselWidget,carousel.columns), XtRImmediate,(XtPointer) 2 },

	{XmNrows,XmCRows, XmRInt,sizeof(int),
	XtOffset(CarouselWidget,carousel.rows), XtRImmediate,(XtPointer) 2 },

	{XmNmargin,XmCMargin, XmRInt,sizeof(int),
	XtOffset(CarouselWidget,carousel.margin), XtRImmediate,(XtPointer) 2 },

    /* Behavior of special children */

	{XmNradioBehavior, XmCRadioBehavior, XtRBoolean, sizeof(Boolean),
	XtOffset(CarouselWidget, carousel.radio_behaviour), XtRImmediate, (XtPointer) True }
};

#ifndef USE_MANAGER
#define USE_MANAGER
#endif


CarouselClassRec carouselClassRec = {
	{
	/* core_class fields  */
	(WidgetClass) &xmDrawingAreaClassRec,/* superclass         */
#if 0
	(WidgetClass) &constraintClassRec,/* superclass         */
#endif
	"Carousel",                           /* class_name         */
	sizeof(CarouselRec),                /* widget_size        */
	NULL,                             /* class_init         */
	NULL,                             /* class_part_init    */
	FALSE,                            /* class_inited       */
	Initialize,                       /* initialize         */
	NULL,                             /* initialize_hook    */
	XtInheritRealize,                 /* realize            */
	NULL,                             /* actions            */
	0,                                /* num_actions        */
	resources,                        /* resources          */
	XtNumber(resources),              /* num_resources      */
	NULLQUARK,                        /* xrm_class          */
	TRUE,                             /* compress_motion    */
	TRUE,                             /* compress_exposure  */
	TRUE,                             /* compress_enterleave*/
	TRUE,                             /* visible_interest   */
	Destroy,                          /* destroy            */
	Resize,                           /* resize             */
	(XtExposeProc)_XmRedisplayGadgets,/* expose             */

	SetValues,                        /* set_values         */
	NULL,                             /* set_values_hook    */
	XtInheritSetValuesAlmost,         /* set_values_almost  */
	NULL,                             /* get_values_hook    */
	NULL,                             /* accept_focus       */
	XtVersion,                        /* version            */
	NULL,                             /* callback_private   */
	XtInheritTranslations,            /* tm_table           */
	NULL,                             /* query_geometry     */
	XtInheritDisplayAccelerator,      /* display_accelerator*/
	NULL,                             /* extension          */
	},
	{
	/* composite_class fields */
	GeometryManager,                 /* geometry_manager    */
	ChangeManaged,                   /* change_managed      */
	InsertChild,                     /* insert_child        */
	DeleteChild,                     /* delete_child        */
	NULL ,                           /* extension           */
	},
	{ 
	/* constraint_class fields */
	NULL,                             /* subresources        */
	0,                                /* subresource_count   */
	0,                                /* constraint_size     */
	NULL,                             /* initialize          */
	NULL,                             /* destroy             */
	NULL,                             /* set_values          */
	NULL,                             /* extension           */
	},
	{
	XtInheritTranslations,   /* default translations */
	NULL,                    /* syn_resources          */
	0,               /* num_syn_resources      */
	NULL,                        /* syn_cont_resources     */
	0,                           /* num_syn_cont_resources */
	XmInheritParentProcess,  /* parent_process */
	NULL,                        /* extension              */

	},
	{
	NULL,
	},
	{
	/* Carousel class fields */
	0,                               /* ignore              */
	}
};





WidgetClass carouselWidgetClass = (WidgetClass) &carouselClassRec;

static void Initialize(request, new)
CarouselWidget request, new;
{
	Arg   al[10];
	int   ac;
	/*
   * Make sure the widget's width and height are 
   * greater than zero.
   */
	if (request->core.width <= 0)
		new->core.width = 5;
	if (request->core.height <= 0)
		new->core.height = 5;

	ac = 0;
	XtSetArg(al[ac], XmNarrowDirection, (XtArgVal) 2); ac++;
	XtSetArg(al[ac], XmNshadowThickness, (XtArgVal) 0); ac++;
	XtSetArg(al[ac], XmNhighlightThickness, (XtArgVal) 0); ac++;
	new->carousel.left       = XtCreateManagedWidget("left_arrow", 
			xmArrowButtonWidgetClass, (Widget) new, al, ac);
	XtAddCallback(new->carousel.left, XmNactivateCallback, left, 
			      (XtPointer) new);
	ac = 0;
	XtSetArg(al[ac], XmNarrowDirection, (XtArgVal) 3); ac++;
	XtSetArg(al[ac], XmNshadowThickness, (XtArgVal) 0); ac++;
	XtSetArg(al[ac], XmNhighlightThickness, (XtArgVal) 0); ac++;
	new->carousel.right      = XtCreateManagedWidget("right_arrow", 
			xmArrowButtonWidgetClass, (Widget) new, al, ac);
	XtAddCallback(new->carousel.right, XmNactivateCallback, right, 
				  (XtPointer) new);
	ac = 0;
	new->carousel.line      = XtCreateManagedWidget("right_arrow", 
			xmSeparatorWidgetClass, (Widget) new, al, ac);

	new->carousel.offset = 0;
}

static void Destroy(w)
CarouselWidget w;
{
}

static void Resize(w)
CarouselWidget w;
{
	new_layout(w, True);
}

static void left(Widget w, CarouselWidget o, XmArrowButtonCallbackStruct*cb)
{
	scroll(o, 1);

}
static void right(Widget w, CarouselWidget o, XmArrowButtonCallbackStruct*cb)
{
	scroll(o, -1);

}
static void place(CarouselWidget w)
{
	int i, j;
	Dimension width  = 4;
	Dimension height = 4;
	Position  x, y;

	for (i = 0; i < w->composite.num_children;  i++) { 
		Widget c = w->composite.children[i];
		if (XtIsManaged(c) && c != w->carousel.line 
			&& c != w->carousel.right && c != w->carousel.left) {
			width  = MAX(width, c->core.width);
			height = MAX(height, c->core.height);
		}
	}
	x = w->carousel.offset;
	y = 0;

	j = 0;

	for (i = 0; i < w->composite.num_children;  i++) { 
		Widget c = w->composite.children[i];
		if (XtIsManaged(c) && c != w->carousel.line 
			&& c != w->carousel.right && c != w->carousel.left) {
			_XmConfigureObject((RectObj)c, x, y, width, height, 0);
			y += height;
			j++;
			if ((j%w->carousel.rows) == 0) {
				x += width;
				y = 0;
			}
		}
	}
	w->carousel.child_width  = width;
	w->carousel.child_height = height;
}

static void scroll(CarouselWidget w, int direction)
{
	int width = w->carousel.child_width;
	int i;
	w->carousel.offset += direction * (width%w->carousel.speed);

	for (i = 0; i < w->carousel.speed;  i++) { 
		w->carousel.offset += direction * width/w->carousel.speed;
		place(w);
		XmUpdateDisplay((Widget)w);
	} 
	show_arrows(w);

}

static Boolean SetValues(current, request, new)
CarouselWidget current, request, new;
{
	if (new->carousel.radio_behaviour && !current->carousel.radio_behaviour)
		force_radio_behaviour(new, False, NULL);

	/* should do something */
	return (False);
}


static XtGeometryResult GeometryManager(w, request, reply)
Widget               w;
XtWidgetGeometry    *request;
XtWidgetGeometry    *reply;
{
/*
	new_layout(XtParent(w));
*/
	return (XtGeometryYes);
}

static void ChangeManaged(tw)
CarouselWidget tw;
{
	new_layout(tw, False);
	force_radio_behaviour(tw, False, NULL);
}


static void new_layout(tw, resize)
CarouselWidget   tw;
Boolean          resize;
{
	Dimension width;
	Dimension height, height_rc;
	Position  left_x, right_x;
	
	Widget    left = tw->carousel.left;
	Widget    right = tw->carousel.right;
	Widget    line = tw->carousel.line;

	place(tw);
/* Size */ 
	height_rc = tw->carousel.child_height*tw->carousel.rows;
	height = height_rc + left->core.height;
	width  = tw->carousel.child_width * tw->carousel.columns;

	if(tw->core.width != width || tw->core.height != height)
	{
		Dimension           maxWidth = width, maxHeight = height;
		XtGeometryResult    result;
		Dimension           replyWidth = 0, replyHeight = 0;

		result = XtMakeResizeRequest(
		    (Widget)tw,
		    maxWidth,
		    maxHeight, 
		    &replyWidth, &replyHeight);

		if (result == XtGeometryAlmost)
			XtMakeResizeRequest (
			    (Widget)tw, 
			    replyWidth, 
			    replyHeight,NULL, NULL);
	}
	XtMoveWidget(left, 0, height_rc);
	XtMoveWidget(right,width-right->core.width,height_rc);
	XtConfigureWidget(line,0, height_rc + left->core.height/2, 
				 width, line->core.height, 0);

	show_arrows(tw);

}

Widget CreateCarousel(par,nam,al,ac)
Widget par;
char  *nam;
Arg   *al;
int   ac;
{
	return   XtCreateWidget(nam,carouselWidgetClass,par,al,ac);
}

static void	show_arrows(CarouselWidget w)
{
	int child = w->composite.num_children - 3;
	int col   = (child + w->carousel.rows - 1)/w->carousel.rows;
	int width  = (col - w->carousel.columns) * w->carousel.child_width;

	if (w->carousel.offset >= 0) XtUnmanageChild(w->carousel.left);
	else XtManageChild(w->carousel.left);

	if (w->carousel.offset <= -width ) XtUnmanageChild(w->carousel.right);
	else XtManageChild(w->carousel.right);


}

static void force_radio_behaviour(CarouselWidget parent, int keep_wset, Widget wset)
{
	Widget child;
	int i, set, set_found = 0;

	if (!parent->carousel.radio_behaviour) return;

	for (i = 0; i < parent->composite.num_children;  i++) { 
		child = parent->composite.children[i];
		set = widget_is_set(child);
		if (set) {
			if ((keep_wset && wset != child) || set_found) widget_reset(child);
			else set_found = 1;
		}
	}
}		

static int widget_is_set(Widget w)
{
	if (!XmIsToggleButton(w) && !XmIsToggleButtonGadget(w))
		return 0;

	return (int) XmToggleButtonGetState(w);
}

static void widget_reset(Widget w)
{
	if (!XmIsToggleButton(w) && !XmIsToggleButtonGadget(w))
		return;

	XmToggleButtonSetState(w, False, True);
}

static void InsertChild(Widget w)
{
	Widget parent = XtParent(w);

	/* Call the composite method */

	(* ( (CompositeWidgetClass) (carouselWidgetClass->core_class.superclass) )->composite_class.insert_child) (w);

	if (XmIsToggleButton(w)) {
		XtAddCallback(w,
			XmNvalueChangedCallback,
			toggle_action, 
			(XtPointer) NULL);
	}
}

static void DeleteChild(Widget w)
{
	if (XmIsToggleButton(w)) {
		XtRemoveCallback(w,
			XmNvalueChangedCallback,
			toggle_action, 
			(XtPointer) NULL);
	}

	/* call the composite method */

	(* ( (CompositeWidgetClass) (carouselWidgetClass->core_class.superclass) )->composite_class.delete_child) (w);

}

static void toggle_action(Widget w, caddr_t udate, XmToggleButtonCallbackStruct* cdata)
{
	CarouselWidget parent = (CarouselWidget)XtParent(w);
	if (parent->carousel.radio_behaviour)
	{
		if (cdata->set)
			force_radio_behaviour(parent, True, w);
	}
}
