#include <classes/BOOPSI/gadgets.h>
#include <classes/BOOPSI/boopsilib.h>

#include <intuition/imageclass.h>
#include <classes/boopsi/popupmenuclass.h>
#include <string.h>
#include <exec/memory.h>

#pragma -
#include <pragma/utility_lib.h>
#include <clib/alib_protos.h>
#include <pragma/intuition_lib.h>
#include <pragma/graphics_lib.h>
#include <pragma/layers_lib.h>
#include <pragma/exec_lib.h>
#include <graphics/gfxmacros.h>
#pragma +

struct InstanceData {
	STRPTR *labels;
	UWORD active;
	struct TextAttr *tattr;
	ULONG minmenu;
	ULONG columns;
	ULONG labelplace;
	UBYTE labelmark;
	UBYTE pad;
// private:
	Object *frame;
	Object *menu;
	struct TextFont *font;
	BOOL menushown;
	UWORD labelCount;
	WORD leftborder;
	WORD rightborder;
	WORD bottomborder;
	WORD topborder;
	struct LabelText *label;
};

#define MENULEFT 21

CycleClass::CycleClass()
	: BoopsiClass(sizeof(struct InstanceData),(ULONG (*)()) &dispatcher,GADGETCLASS,NULL)
{
}

ULONG CycleClass::dispatcher(Class *cl, Object *o, Msg msg)
{
	struct InstanceData *data;
	ULONG retval = FALSE;
	switch (msg->MethodID)
	{
		case OM_NEW:
		{
			Object *object;
			if (object = (Object *) DoSuperMethodA(cl,o,msg))
			{
				data = (struct InstanceData *) INST_DATA(cl,object);
				LONG oleft = 0;
				LONG owidth = 1;
				LONG otop = 0;
				LONG oheight = 1;
				data->labels = NULL;
				data->active = 0;
				data->tattr = NULL;
				data->minmenu = 3;
				data->font = NULL;
				data->labelCount = 0;
				data->columns = 0;
				data->labelplace = CY_LabelLeft;
				data->labelmark = '_';
				TagItemCursorC tic(((struct opSet *) msg)->ops_AttrList);
				while (!tic.isDone()) {
					/* register */ ULONG ticData = tic.itemData();
					switch (tic.itemTag())
					{
						case GA_Left:
							oleft = ticData;
							break;
						case GA_Width:
							owidth = ticData;
							break;
						case GA_Top:
							otop = ticData;
							break;
						case GA_Height:
							oheight = ticData;
							break;
						case CYA_Labels:
							data->labels = (STRPTR *) ticData;
							break;
						case CYA_Active:
							data->active = (UWORD) ticData;
							break;
						case CYA_Font:
							data->tattr = (struct TextAttr *) ticData;
							break;
						case CYA_MinMenu:
							data->minmenu = (ULONG) ticData;
							break;
						case CYA_Columns:
							data->columns = (ULONG) ticData;
							break;
						case CYA_LabelPlace:
							data->labelplace = (ULONG) ticData;
							break;
						case CYA_LabelMark:
							data->labelmark = (UBYTE) ticData;
							break;
						default:
							break;
					};
					tic.next();
				};
				data->frame = (Object *) NewObject(NULL,FRAMEICLASS,
					IA_Left, oleft,
					IA_Top, otop,
					IA_Width, owidth,
					IA_Height, oheight,
					IA_EdgesOnly, FALSE,
					IA_Recessed, FALSE,
					IA_FrameType, FRAME_BUTTON,
					TAG_END);
				data->menu = (Object *) NewObject(
					BPopupMenuC::boopsiclass()->boopsiclass(),NULL,
					GA_Left, oleft + MENULEFT,
					GA_Top, otop,
					GA_Width, owidth - MENULEFT,
					GA_Height, oheight,
					MA_Labels, data->labels,
					MA_Font, data->tattr,
					TAG_END);
				if (data->tattr)
				{
					data->font = OpenFont(data->tattr);
				};
				if (data->labels)
				{
					int i = 0;
					while (data->labels[i]) {
						i++;
					};
					data->labelCount = i;
				};
				if (data->active >= data->labelCount)
				{
					data->active = 0;
				};
				data->label = AllocLText(
					(STRPTR) ((struct Gadget *) object)->GadgetText,
					data->tattr,1,0,JAM2,data->labelmark);
				calcBorders(cl,(struct Gadget *) object, data);
				retval = (ULONG) object;
			};
			break;
		};
		case OM_DISPOSE:
		{
			data = (struct InstanceData *) INST_DATA(cl,o);
			if (data->frame)
			{
				DisposeObject(data->frame);
			};
			if (data->font)
			{
				CloseFont(data->font);
			};
			FreeLText(data->label);
			retval = DoSuperMethodA(cl,o,msg);
			break;
		};
		case OM_GET:
		{
			data = (struct InstanceData *) INST_DATA(cl,o);
			switch (((struct opGet *) msg)->opg_AttrID)
			{
				case GA_Left:
					*(((struct opGet *) msg)->opg_Storage) = ((struct Gadget *) o)->LeftEdge;
					retval = TRUE;
					break;
				case GA_Top:
					*(((struct opGet *) msg)->opg_Storage) = ((struct Gadget *) o)->TopEdge;
					retval = TRUE;
					break;
				case GA_Width:
					*(((struct opGet *) msg)->opg_Storage) = ((struct Gadget *) o)->Width;
					retval = TRUE;
					break;
				case GA_Height:
					*(((struct opGet *) msg)->opg_Storage) = ((struct Gadget *) o)->Height;
					retval = TRUE;
					break;
				case CYA_Font:
					*(((struct opGet *) msg)->opg_Storage) = (ULONG) data->tattr;
					retval = TRUE;
					break;
				case CYA_Labels:
					*(((struct opGet *) msg)->opg_Storage) = (ULONG) data->labels;
					retval = TRUE;
					break;
				case CYA_Active:
					*(((struct opGet *) msg)->opg_Storage) = (ULONG) data->active;
					retval = TRUE;
					break;
				case CYA_LeftBorder:
					*(((struct opGet *) msg)->opg_Storage) = (ULONG) data->leftborder;
					retval = TRUE;
					break;
				case CYA_RightBorder:
					*(((struct opGet *) msg)->opg_Storage) = (ULONG) data->rightborder;
					retval = TRUE;
					break;
				case CYA_TopBorder:
					*(((struct opGet *) msg)->opg_Storage) = (ULONG) data->topborder;
					retval = TRUE;
					break;
				case CYA_BottomBorder:
					*(((struct opGet *) msg)->opg_Storage) = (ULONG) data->bottomborder;
					retval = TRUE;
					break;
				case CYA_AutoWidth:
					*(((struct opGet *) msg)->opg_Storage) =
						calcWidth(cl,(struct Gadget *) o,data);
					retval = TRUE;
					break;
				case CYA_AutoHeight:
					*(((struct opGet *) msg)->opg_Storage) =
						calcHeight(cl,(struct Gadget *) o,data);
					retval = TRUE;
					break;
				case CYA_Columns:
					*(((struct opGet *) msg)->opg_Storage) = (ULONG) data->columns;
					retval = TRUE;
					break;
				case CYA_MinMenu:
					*(((struct opGet *) msg)->opg_Storage) = (ULONG) data->minmenu;
					retval = TRUE;
					break;
				case CYA_LabelPlace:
					*(((struct opGet *) msg)->opg_Storage) = (ULONG) data->labelplace;
					retval = TRUE;
					break;
				case CYA_LabelMark:
					*(((struct opGet *) msg)->opg_Storage) = (ULONG) data->labelmark;
					retval = TRUE;
					break;
				default:
					retval = DoSuperMethodA(cl,o,msg);
					break;
			};
			break;
		};
		case OM_SET:
		{
			data = (struct InstanceData *) INST_DATA(cl,o);
			BOOL needsRendering = FALSE;
			TagItemCursorC tic(((struct opSet *) msg)->ops_AttrList);
			while (!tic.isDone()) {
				/* register */ ULONG ticData = tic.itemData();
				switch (tic.itemTag())
				{
					case GA_Left:
						if (data->menu)
							SetAttrs(data->menu,GA_Left,ticData+MENULEFT,TAG_END);
						break;
					case GA_Top:
						if (data->menu)
							SetAttrs(data->menu,GA_Top,ticData,TAG_END);
						break;
					case GA_Width:
						if (data->menu)
							SetAttrs(data->menu,GA_Width,ticData-MENULEFT,TAG_END);
						break;
					case GA_Height:
						if (data->menu)
							SetAttrs(data->menu,GA_Height,ticData,TAG_END);
						break;
					case CYA_Font:
						if (data->font)
						{
							CloseFont(data->font);
							data->font = NULL;
						};
						data->tattr = (struct TextAttr *) ticData;
						if (data->menu)
							SetAttrs(data->menu,MA_Font,ticData,TAG_END);
						if (data->tattr)
						{
							data->font = OpenFont(data->tattr);
						};
						ChangeLTextFont(data->label,data->tattr);
						calcBorders(cl,(struct Gadget *) o, data);
						break;
					case CYA_Labels:
						data->labels = (STRPTR *) ticData;
						if (data->menu)
							SetAttrs(data->menu,MA_Labels,ticData,TAG_END);
						if (data->labels)
						{
							int i = 0;
							while (data->labels[i]) {
								i++;
							};
							data->labelCount = i;
						}
						else {
							data->labelCount = 0;
						};
						needsRendering = TRUE;
						break;
					case CYA_Active:
						data->active = (UWORD) ticData;
						needsRendering = TRUE;
						break;
					case CYA_MinMenu:
						data->minmenu = (ULONG) ticData;
						break;
					case CYA_Columns:
						data->columns = (ULONG) ticData;
						break;
					case CYA_Next:
						(data->active)++;
						if (data->active >= data->labelCount)
							data->active = 0;
						needsRendering = TRUE;
						break;
					case CYA_Prev:
						if (data->active == 0)
							data->active = data->labelCount - 1
						else
							(data->active)--;
						needsRendering = TRUE;
						break;
					default:
						break;
				};
				tic.next();
			};
			retval = DoSuperMethodA(cl,o,msg);
			if (data->active >= data->labelCount)
			{
				data->active = 0;
			};
			if (needsRendering)
			{
				renderCycle(cl,(struct Gadget *) o,
						(struct gpRender *) msg,data);
			};
			break;
		};
		case GM_RENDER:
		{
			data = (struct InstanceData *) INST_DATA(cl,o);
			retval = renderCycle(cl,(struct Gadget *) o,
				(struct gpRender *) msg,data);
			break;
		};
		case GM_HITTEST:
		{
			retval = GMR_GADGETHIT;
			break;
		};
		case GM_GOACTIVE:
		{
			data = (struct InstanceData *) INST_DATA(cl,o);
			if (((struct gpInput *) msg)->gpi_IEvent)
			{
				((struct Gadget *) o)->Flags |= GFLG_SELECTED;
				renderCycle(cl,(struct Gadget *) o,
					(struct gpRender *) msg,data);
				if (((struct gpInput *) msg)->gpi_Mouse.X >= 21
					&& data->minmenu <= data->labelCount)
				{
					if (data->menu)
					{
						((struct gpInput *) msg)->gpi_Mouse.X -= MENULEFT;
						struct InputEvent *ie = ((struct gpInput *) msg)->gpi_IEvent;
						((struct gpInput *) msg)->gpi_IEvent = NULL;
						retval = DoMethodA(data->menu,msg);
						((struct gpInput *) msg)->gpi_IEvent = ie;
					}
					else
						retval = GMR_NOREUSE;
					data->menushown = TRUE;
				}
				else {
					data->menushown = FALSE;
					retval = GMR_MEACTIVE;
				};
			}
			else
				retval = GMR_NOREUSE;
			break;
		};
		case GM_GOINACTIVE:
		{
			data = (struct InstanceData *) INST_DATA(cl,o);
			if (data->menushown)
			{
				if (data->menu)
				{
					ULONG active;
					DoMethodA(data->menu,msg);
					GetAttr(MA_Active,data->menu,&active);
					data->active = active;
				};
				data->menushown = FALSE;
			};
			((struct Gadget *) o)->Flags &= ~GFLG_SELECTED;
			renderCycle(cl,(struct Gadget *) o,
				(struct gpRender *) msg,data);
			break;
		};
		case GM_HANDLEINPUT:
		{
			data = (struct InstanceData *) INST_DATA(cl,o);
			struct gpInput *gpi = (struct gpInput *) msg;
			struct InputEvent *ie = gpi->gpi_IEvent;
			BOOL mouseOverGadget = ((gpi->gpi_Mouse.X >= 0) &&
					(gpi->gpi_Mouse.X < ((struct Gadget *) o)->Width) &&
					(gpi->gpi_Mouse.Y >= 0) &&
					(gpi->gpi_Mouse.Y < ((struct Gadget *) o)->Height));
			if (data->menushown)
			{
				if (data->menu)
				{
					((struct gpInput *) msg)->gpi_Mouse.X -= MENULEFT;
					retval = DoMethodA(data->menu,msg);
				}
				else
					retval = GMR_NOREUSE;
			}
			else {
				if (mouseOverGadget)
				{
					if (!(((struct Gadget *) o)->Flags & GFLG_SELECTED))
					{
						((struct Gadget *) o)->Flags |= GFLG_SELECTED;
						renderCycle(cl,(struct Gadget *) o,
							(struct gpRender *) msg,data);
					};
				};
				else {
					if (((struct Gadget *) o)->Flags & GFLG_SELECTED)
					{
						((struct Gadget *) o)->Flags &= ~GFLG_SELECTED;
						renderCycle(cl,(struct Gadget *) o,
							(struct gpRender *) msg,data);
					};
				};
				switch (ie->ie_Class)
				{
					case IECLASS_RAWMOUSE:
						switch (ie->ie_Code)
						{
							case SELECTUP:
								if (mouseOverGadget)
								{
									if (ie->ie_Qualifier &
										(IEQUALIFIER_LSHIFT|IEQUALIFIER_RSHIFT))
									{
										if (data->active > 0)
											(data->active)--;
										else
											data->active = data->labelCount-1;
										};
									else {
										if (data->active < data->labelCount-1)
											(data->active)++;
										else
											data->active = 0;
									};
									retval = GMR_NOREUSE | GMR_VERIFY;
								}
								else {
									retval = GMR_NOREUSE;
								};
								break;
							case MENUDOWN:
								retval = GMR_REUSE;
								break;
							default:
								retval = GMR_MEACTIVE;
								break;
						};
						break;
					default:
						retval = GMR_MEACTIVE;
						break;
				};
			};
			break;
		};
		default:
		{
			retval = DoSuperMethodA(cl,o,msg);
			break;
		};
	};
	return retval;
}

ULONG CycleClass::renderCycle(Class *cl, struct Gadget *g,
	struct gpRender *msg, struct InstanceData *data)
{
	static UWORD ditherData[] = { 0x1111,0x4444 };

	struct RastPort *rp;
	struct GadgetInfo *ginfo;
	if (msg->MethodID == OM_SET || msg->MethodID == OM_UPDATE)
		ginfo = ((struct opSet *) msg)->ops_GInfo
	else
		ginfo = msg->gpr_GInfo;
	if (!ginfo)
		return TRUE;
	UWORD *pens = ginfo->gi_DrInfo->dri_Pens;
	if (msg->MethodID == GM_RENDER)
		rp = msg->gpr_RPort
	else
		rp = ObtainGIRPort(ginfo);
	if (rp)
	{
		UWORD shine = pens[SHINEPEN];
		UWORD shadow = pens[SHADOWPEN];
		if (data->frame)
		{
			SetAttrs(data->frame,
				IA_Left,g->LeftEdge,
				IA_Top,g->TopEdge,
				IA_Width,g->Width,
				IA_Height,g->Height,
				TAG_END);
			if (g->Flags & GFLG_SELECTED)
			{
				DrawImageState(rp,(struct Image *) data->frame,0,0,
					IDS_SELECTED,ginfo->gi_DrInfo);
				UWORD x = shine;
				shine = shadow;
				shadow = x;
			}
			else {
				DrawImageState(rp,(struct Image *) data->frame,0,0,
					IDS_NORMAL,ginfo->gi_DrInfo);
			};
		};
		UWORD l = g->LeftEdge;
		UWORD t = g->TopEdge;
		UWORD h = g->Height;

		if (g->GadgetText && data->font)
		{
			SetAPen(rp,pens[TEXTPEN]);
			SetBPen(rp,pens[BACKGROUNDPEN]);
			SetDrMd(rp,JAM2);
			ChangeLTextPens(data->label,pens[TEXTPEN],pens[BACKGROUNDPEN],JAM2);
			switch (data->labelplace)
			{
				case CY_LabelLeft:
					PrintLText(rp,data->label,
						g->LeftEdge - data->leftborder, g->TopEdge,
						data->leftborder, g->Height);
					break;
				case CY_LabelRight:
					PrintLText(rp,data->label,
						g->LeftEdge + g->Width, g->TopEdge,
						data->rightborder, g->Height,
						STADJUST_Right);
					break;
			};
		};

		if (data->font && g->Width > 25 && h >= 12 && h >= data->font->tf_YSize+4)
		{
			SetAPen(rp,pens[TEXTPEN]);	// Cycle Image
			SetDrMd(rp,JAM1);
			Move(rp,l+6,t+3);
			Draw(rp,l+6,t+h-5);
			Move(rp,l+7,t+3);
			Draw(rp,l+7,t+h-5);
			Move(rp,l+7,t+2);
			Draw(rp,l+13,t+2);
			Move(rp,l+7,t+h-4);
			Draw(rp,l+13,t+h-4);
			WritePixel(rp,l+13,t+3);
			WritePixel(rp,l+14,t+3);
			Move(rp,l+11,t+4);
			Draw(rp,l+16,t+4);
			Move(rp,l+12,t+5);
			Draw(rp,l+15,t+5);
			WritePixel(rp,l+13,t+6);
			WritePixel(rp,l+14,t+6);
			WritePixel(rp,l+13,t+h-5);
			WritePixel(rp,l+14,t+h-5);

			SetAPen(rp,shadow);			// Trennstrich
			Move(rp,l+20,t+2);
			Draw(rp,l+20,t+h-3);
			SetAPen(rp,shine);
			Move(rp,l+21,t+2);
			Draw(rp,l+21,t+h-3);

			UWORD textwidth = g->Width - 30;
			if (data->labels)
			{
				STRPTR label = data->labels[data->active];
				if (label)
				{
					TextExtent extent;
					SetFont(rp,data->font);
					ULONG c = TextFit(rp,label,strlen(label),&extent,NULL,
						1,textwidth,h-2);
					if (c > 0)
					{
						SetAPen(rp,pens[TEXTPEN]);
						SetDrMd(rp,JAM1);
						Move(rp,l+25+extent.te_Extent.MinX+
							((g->Width - 30 - extent.te_Extent.MinX - extent.te_Width) >> 1),
							t+((h - extent.te_Height) >> 1)- extent.te_Extent.MinY);
						Text(rp,label,c);
					};
				};
			};
		};

		if (g->Flags & GFLG_DISABLED)
		{
			SetDrMd(rp,JAM1);
			SetAPen(rp,pens[TEXTPEN]);
			SetAfPt(rp,ditherData,1);
			RectFill(rp,g->LeftEdge, g->TopEdge,
				g->LeftEdge + g->Width - 1, g->TopEdge + g->Height - 1);
			SetAfPt(rp,NULL,0);
		};

		if (msg->MethodID != GM_RENDER)
			ReleaseGIRPort(rp);
		return TRUE;
	};
	return FALSE;
}

ULONG CycleClass::calcWidth(Class *cl, struct Gadget *, struct InstanceData *data)
{
	if (!data->tattr)
		return 54;
	ULONG width = 0;
	if (data->columns != 0)
	{
		struct IntuiText it = { 0,0,0,0,0,NULL,NULL,NULL };
		it.ITextFont = data->tattr;
		it.IText = "0";
		width = data->columns*IntuiTextLength(&it);
	}
	else {
		STRPTR *labels = data->labels;
		if (labels == NULL || *labels == NULL)
		{
			struct IntuiText it = { 0,0,0,0,0,NULL,NULL,NULL };
			it.ITextFont = data->tattr;
			it.IText = "0";
			width = 15*IntuiTextLength(&it);
		}
		else {
			while (*labels != NULL)
			{
				struct IntuiText it = { 0,0,0,0,0,NULL,NULL,NULL };
				it.ITextFont = data->tattr;
				it.IText = *labels;
				LONG w = IntuiTextLength(&it);
				if (w > width)
					width = w;
				labels++;
			};
		};
	};
	if (width < 24)
		width = 24;
	return width + 38;
}

ULONG CycleClass::calcHeight(Class *cl, struct Gadget *, struct InstanceData *data)
{
	if (!data->tattr)
		return 12;
	return data->tattr->ta_YSize + 6;
}

VOID CycleClass::calcBorders(Class *cl, struct Gadget *g,
		struct InstanceData *data)
{
	data->leftborder = 0;
	data->rightborder = 0;
	data->topborder = 0;
	data->bottomborder = 0;

	if (data->tattr && g->GadgetText)
	{
		switch (data->labelplace)
		{
			case CY_LabelLeft:
				data->leftborder = MeasureLText(data->label) + 4;
				break;
			case CY_LabelRight:
				data->rightborder = MeasureLText(data->label) + 4;
				break;
		};
	};
}

// *************************************************************

BCycleC::BCycleC(GadgetEventC *ev, WindowC &w, stringarray &l, Tag tag1type, ...)
	: BGadgetC(ev,pmc.boopsiclass(),NULL,w,(struct TagItem *) &tag1type),
	  GCycleC(),
	  labels(l)
{
	setAttrs(CYA_Labels,labels.strings(),TAG_END);
}

BCycleC::BCycleC(GadgetEventC *ev,WindowC &w, stringarray &l, struct TagItem *tags)
	: BGadgetC(ev,pmc.boopsiclass(),NULL,w,tags),
	  GCycleC(),
	  labels(l)
{
	setAttrs(CYA_Labels,labels.strings(),TAG_END);
}

VOID BCycleC::setLabels(STRPTR *l)
{
	labels = stringarray(l);
	setAttrs(CYA_Labels,labels.strings(),TAG_END);
}

VOID BCycleC::setLabels(STRPTR label1, ...)
{
	labels = stringarray(&label1);
	setAttrs(CYA_Labels,labels.strings(),TAG_END);
}

STRPTR *BCycleC::getLabels()
{
	return labels.strings();
}

ULONG BCycleC::labelsCount()
{
	return labels.length();
}

UWORD BCycleC::addLabel(STRPTR label)
{
	UWORD c = labels.length();
	labels.addTail(label);
	setAttrs(CYA_Labels,labels.strings(),TAG_END);
	return c;
}

VOID BCycleC::remLabel(UWORD count)
{
	labels.remove(count);
	setAttrs(CYA_Labels,labels.strings(),TAG_END);
}

CycleClass BCycleC::pmc;

// *************************************************************

