#include "BOOPSILib.h"

#ifndef INTUITION_GADGETCLASS_H
#include <intuition/gadgetclass.h>
#endif

#define MYLIB_BOOPSI
#define ROM_VERSION 39

#include "MyLib.h"

#include "StaticSavedsAsmA0A1A2.h"

/****** Group/--background-- *********************************************
*
*	Group gadgets are the basic elements of the layout process.
*	A group gadget contains several gadgets (children). The
*	children are arranged in a row (horizontal groups) or in
*	a column (vertical group).
*
*	Groups can, of course, contain other groups.
*
*	Parent class: gadgetclass
*
*************************************************************************/

struct InstanceData
{
  struct MinList Children;
  struct TextFont *Font;
  struct
    {
      unsigned int JustifyRight:1;
      unsigned int JustifyLeft:1;
      unsigned int Horizontal:1;
      unsigned int SameSize:1;
    } Flags;
  struct BOOPSILibS_LayoutInfo LayoutInfo;
  struct BOOPSILibS_Fraction Spacing;
};

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

struct IClass *BOOPSILibC_Group;

/****** Group/BOOPSILibA_Group_Child *************************************
*************************************************************************/

/****** Group/BOOPSILibA_Group_Justify ***********************************
*************************************************************************/

/****** Group/BOOPSILibA_Group_Spacing ***********************************
*
*    NAME
*	BOOPSILibA_Group_Spacing -- spacing between children
*
*    SYNOPSIS
*	Type:		struct BOOPSILibS_Fraction
*	Usability:	I..
*
*    FUNCTION
*	Specify the spacing between the children of the group.
*	The spacing is fixed, and calculated specified as a fraction
*	of the font height.
*
*************************************************************************/

/****** Group/BOOPSILibA_Group_Horizontal ********************************
*
*    NAME
*	BOOPSILibA_Group_Horizontal -- create horizontal group
*
*    SYNOPSIS
*	Type:		ULONG
*	Usability:	I..
*	Values:		FALSE -> vertical group
*	                TRUE  -> horizontal group
*	Default:	FALSE
*
*************************************************************************/

/****** Group/BOOPSILibA_Group_SameSize **********************************
*
*    NAME
*	BOOPSILibA_Group_SameSize -- make all objects the same size
*
*    SYNOPSIS
*	Type:		ULONG
*	Usability:	I..
*	Values:		FALSE
*	                TRUE   -> all objects same size
*	Default:        FALSE
*
*************************************************************************/

METHOD(OM_NEW,struct opSet *)

{
  if ((TheObject=(Object *)SUPER_METHOD)!=NULL)
    {
      struct InstanceData *InstanceData;
      struct TagItem *Tags;
      const struct TagItem *Tag;
      int Error;

      ((struct ExtGadget *)TheObject)->LeftEdge=0;
      ((struct ExtGadget *)TheObject)->TopEdge=0;
      ((struct ExtGadget *)TheObject)->Width=0;
      ((struct ExtGadget *)TheObject)->Height=0;
      InstanceData=INSTANCEDATA;
      NewList((struct List *)&InstanceData->Children);
      InstanceData->Flags.Horizontal=FALSE;
      InstanceData->Flags.JustifyRight=FALSE;
      InstanceData->Flags.JustifyLeft=FALSE;
      InstanceData->Flags.SameSize=FALSE;
      InstanceData->Spacing.Numerator=0;
      InstanceData->Spacing.Denominator=1;
      InstanceData->LayoutInfo.Weight=100;
      Error=FALSE;
      Tags=Message->ops_AttrList;
      while ((Tag=NextTagItem(&Tags))!=NULL)
	{
	  switch (Tag->ti_Tag)
	    {
	    case BOOPSILibA_Group_Horizontal:
	      InstanceData->Flags.Horizontal=!!Tag->ti_Data;
	      break;

	    case BOOPSILibA_Group_SameSize:
	      InstanceData->Flags.SameSize=!!Tag->ti_Data;
	      break;

	    case BOOPSILibA_Group_Child:
	      if ((Object *)Tag->ti_Data!=NULL)
		{
		  DoMethod(TheObject,OM_ADDMEMBER,(Object *)Tag->ti_Data);
		}
	      else
		{
		  Error=TRUE;
		}
	      break;

	    case BOOPSILibA_Group_Spacing:
	      InstanceData->Spacing=*(const struct BOOPSILibS_Fraction *)&Tag->ti_Data;
	      break;

	    case BOOPSILibA_Weight:
	      InstanceData->LayoutInfo.Weight=(WORD)Tag->ti_Data;
	      break;

	    case BOOPSILibA_Group_Justify:
	      if ((LONG)Tag->ti_Data==0)
		{
		  InstanceData->Flags.JustifyRight=FALSE;
		  InstanceData->Flags.JustifyLeft=FALSE;
		}
	      else if ((LONG)Tag->ti_Data<0)
		{
		  InstanceData->Flags.JustifyRight=FALSE;
		  InstanceData->Flags.JustifyLeft=TRUE;
		}
	      else
		{
		  InstanceData->Flags.JustifyRight=TRUE;
		  InstanceData->Flags.JustifyLeft=FALSE;
		}
	      break;
	    }
	}
      if (Error)
	{
	  CoerceMethod(TheClass,TheObject,OM_DISPOSE);
	  TheObject=NULL;
	}
    }
  return (ULONG)TheObject;
}

/****** Group/OM_DISPOSE *************************************************
*
*************************************************************************/

METHOD(OM_DISPOSE,Msg)

{
  {
    APTR Children;
    Object *Child;

    Children=INSTANCEDATA->Children.mlh_Head;
    while ((Child=NextObject(&Children))!=NULL)
      {
	DoMethod(TheObject,OM_REMMEMBER,Child);
	DisposeObject(Child);
      }
  }
  return SUPER_METHOD;
}

/****** Group/OM_ADDMEMBER ***********************************************
*
*************************************************************************/

METHOD(OM_ADDMEMBER,struct opMember *)

{
  return DoMethod(Message->opam_Object,OM_ADDTAIL,&INSTANCEDATA->Children);
}

/****** Group/OM_REMMEMBER ***********************************************
*
*************************************************************************/

METHOD(OM_REMMEMBER,struct opMember *)

{
  return DoMethod(Message->opam_Object,OM_REMOVE);
}

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

METHOD(BOOPSILibM_Init,struct BOOPSILibP_Init *)

{
  struct InstanceData *InstanceData;
  struct BOOPSILibS_MinMax *MyLayoutSize;
  struct BOOPSILibS_MinMax *MyOtherSize;
  APTR Children;
  Object *Child;
  WORD ChildCount;
  WORD MaxSize;

  DoMethod(Message->Root,BOOPSILibM_Root_AddGadget,(struct Gadget *)TheObject);
  InstanceData=INSTANCEDATA;
  InstanceData->Font=Message->Font;
  InstanceData->LayoutInfo.Width.Min=0;
  InstanceData->LayoutInfo.Width.Max=0;
  InstanceData->LayoutInfo.Height.Min=0;
  InstanceData->LayoutInfo.Height.Max=0;
  if (InstanceData->Flags.Horizontal)
    {
      MyLayoutSize=&InstanceData->LayoutInfo.Width;
      MyOtherSize=&InstanceData->LayoutInfo.Height;
    }
  else
    {
      MyLayoutSize=&InstanceData->LayoutInfo.Height;
      MyOtherSize=&InstanceData->LayoutInfo.Width;
    }
  ChildCount=0;
  MaxSize=0;
  Children=InstanceData->Children.mlh_Head;
  while ((Child=NextObject(&Children))!=NULL)
    {
      struct BOOPSILibS_LayoutInfo *LayoutInfo;
      struct BOOPSILibS_MinMax *LayoutSize;
      struct BOOPSILibS_MinMax *OtherSize;
      LONG t;

      LayoutInfo=(struct BOOPSILibS_LayoutInfo *)DoMethodA(Child,Message);
      if (InstanceData->Flags.Horizontal)
	{
	  LayoutSize=&LayoutInfo->Width;
	  OtherSize=&LayoutInfo->Height;
	}
      else
	{
	  LayoutSize=&LayoutInfo->Height;
	  OtherSize=&LayoutInfo->Width;
	}
      if (InstanceData->Flags.SameSize)
	{
	  if (LayoutSize->Min>MaxSize)
	    {
	      MyLayoutSize->Min+=(LayoutSize->Min-MaxSize)*ChildCount;
	      MaxSize=LayoutSize->Min;
	    }
	  MyLayoutSize->Min+=MaxSize;
	}
      else
	{
	  MyLayoutSize->Min+=LayoutSize->Min;
	}
      t=MyLayoutSize->Max+LayoutSize->Max;
      if (t>MAX_WORD || t<0)
	{
	  t=MAX_WORD;
	}
      MyLayoutSize->Max=t;
      if (MyOtherSize->Min<OtherSize->Min)
	{
	  MyOtherSize->Min=OtherSize->Min;
	}
      if (MyOtherSize->Max<OtherSize->Max)
	{
	  MyOtherSize->Max=OtherSize->Max;
	}
      ChildCount++;
    }

  {
    WORD t;

    t=(ChildCount-1)*(InstanceData->Font->tf_YSize*InstanceData->Spacing.Numerator/InstanceData->Spacing.Denominator);
    MyLayoutSize->Min+=t;
    t+=MyLayoutSize->Max;
    if (t>MAX_WORD || t<0)
      {
	t=MAX_WORD;
      }
    MyLayoutSize->Max=t;
  }

  return (ULONG)&InstanceData->LayoutInfo;
}

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

static INLINE void CalcSizes(struct InstanceData *InstanceData, ULONG TotalAddSpace, ULONG TotalWeight, WORD MaxSize)

{
  APTR State;
  Object *Child;
  ULONG CurrentWeight;
  ULONG CurrentAddSpace;
  int SecondPass;

  SecondPass=FALSE;
 Start:
  CurrentWeight=0;
  CurrentAddSpace=0;
  State=InstanceData->Children.mlh_Head;
  while ((Child=NextObject(&State))!=NULL)
    {
      struct BOOPSILibS_LayoutInfo *LayoutInfo;
      struct BOOPSILibS_MinMax *Size;

      LayoutInfo=(struct BOOPSILibS_LayoutInfo *)AttrGet(Child,BOOPSILibA_LayoutInfo);
      if (InstanceData->Flags.Horizontal)
	{
	  Size=&LayoutInfo->Width;
	}
      else
	{
	  Size=&LayoutInfo->Height;
	}
      if (LayoutInfo->Group==0)
	{
	  ULONG PrevAddSpace;
	  WORD NewSize;

	  if (InstanceData->Flags.SameSize)
	    {
	      Size->Min=MaxSize;
	    }
	  CurrentWeight+=LayoutInfo->Weight;
	  PrevAddSpace=CurrentAddSpace;
	  CurrentAddSpace=(TotalAddSpace*CurrentWeight)/TotalWeight;
	  NewSize=Size->Min+(CurrentAddSpace-PrevAddSpace);
	  if (NewSize>Size->Max)
	    {
	      LayoutInfo->Group=Size->Max;
	      TotalWeight-=LayoutInfo->Weight;
	      TotalAddSpace-=Size->Max-Size->Min;
	      goto Start;
	    }
	  if (SecondPass)
	    {
	      LayoutInfo->Group=NewSize;
	    }
	}
      else
	{
	  CurrentAddSpace+=LayoutInfo->Group-Size->Min;
	}
    }
  if (!SecondPass)
    {
      SecondPass=TRUE;
      goto Start;
    }
}

/****i* Group/BOOPSILibM_Layout ******************************************
*
*************************************************************************/

METHOD(BOOPSILibM_Layout,const struct BOOPSILibP_Layout *)

{
  struct InstanceData *InstanceData;
  ULONG TotalWeight;
  ULONG TotalAddSpace;
  WORD ChildCount;
  WORD Spacing;
  WORD MaxSize;

  InstanceData=INSTANCEDATA;

  {
    APTR State;
    Object *Child;

    TotalWeight=0;
    ChildCount=0;
    MaxSize=0;
    State=InstanceData->Children.mlh_Head;
    while ((Child=NextObject(&State))!=NULL)
      {
	struct BOOPSILibS_LayoutInfo *LayoutInfo;

	LayoutInfo=(struct BOOPSILibS_LayoutInfo *)AttrGet(Child,BOOPSILibA_LayoutInfo);
	TotalWeight+=LayoutInfo->Weight;
	LayoutInfo->Group=0;
	if (InstanceData->Flags.SameSize)
	  {
	    if (InstanceData->Flags.Horizontal)
	      {
		if (LayoutInfo->Width.Min>MaxSize)
		  {
		    MaxSize=LayoutInfo->Width.Min;
		  }
	      }
	    else
	      {
		if (LayoutInfo->Height.Min>MaxSize)
		  {
		    MaxSize=LayoutInfo->Height.Min;
		  }
	      }
	  }
	ChildCount++;
      }
  }

  if (TotalWeight==0)
    {
      TotalWeight=1;
    }

  if (InstanceData->Flags.Horizontal)
    {
      TotalAddSpace=Message->Position.Width-InstanceData->LayoutInfo.Width.Min;
    }
  else
    {
      TotalAddSpace=Message->Position.Height-InstanceData->LayoutInfo.Height.Min;
    }
  Spacing=InstanceData->Font->tf_YSize*InstanceData->Spacing.Numerator/InstanceData->Spacing.Denominator;

  CalcSizes(InstanceData,TotalAddSpace,TotalWeight,MaxSize);

  {
    APTR State;
    Object *Child;
    struct BOOPSILibP_Layout Layout;

    Layout.MethodID=BOOPSILibM_Layout;
    Layout.Root=Message->Root;
    Layout.GadgetInfo=Message->GadgetInfo;
    if (InstanceData->Flags.Horizontal)
      {
	Layout.Position.Left=Message->Position.Left;
      }
    else
      {
	Layout.Position.Top=Message->Position.Top;
      }
    State=InstanceData->Children.mlh_Head;
    while ((Child=NextObject(&State))!=NULL)
      {
	struct BOOPSILibS_LayoutInfo *LayoutInfo;

	LayoutInfo=(struct BOOPSILibS_LayoutInfo *)AttrGet(Child,BOOPSILibA_LayoutInfo);
	if (InstanceData->Flags.Horizontal)
	  {
	    Layout.Position.Width=LayoutInfo->Group;
	    if (Message->Position.Height<=LayoutInfo->Height.Max)
	      {
		Layout.Position.Height=Message->Position.Height;
		Layout.Position.Top=Message->Position.Top;
	      }
	    else
	      {
		Layout.Position.Height=LayoutInfo->Height.Max;
		if (InstanceData->Flags.JustifyRight)
		  {
		    Layout.Position.Top=Message->Position.Top+Message->Position.Height-Layout.Position.Height;
		  }
		else if (InstanceData->Flags.JustifyLeft)
		  {
		    Layout.Position.Top=Message->Position.Top;
		  }
		else
		  {
		    Layout.Position.Top=Message->Position.Top+(Message->Position.Height-Layout.Position.Height)/2;
		  }
	      }
	    DoMethodA(Child,&Layout);
	    Layout.Position.Left+=Layout.Position.Width+Spacing;
	  }
	else
	  {
	    Layout.Position.Height=LayoutInfo->Group;
	    if (Message->Position.Width<=LayoutInfo->Width.Max)
	      {
		Layout.Position.Width=Message->Position.Width;
		Layout.Position.Left=Message->Position.Left;
	      }
	    else
	      {
		Layout.Position.Width=LayoutInfo->Width.Max;
		if (InstanceData->Flags.JustifyRight)
		  {
		    Layout.Position.Left=Message->Position.Left+Message->Position.Width-Layout.Position.Width;
		  }
		else if (InstanceData->Flags.JustifyLeft)
		  {
		    Layout.Position.Left=Message->Position.Left;
		  }
		else
		  {
		    Layout.Position.Left=Message->Position.Left+(Message->Position.Width-Layout.Position.Width)/2;
		  }
	      }
	    DoMethodA(Child,&Layout);
	    Layout.Position.Top+=Layout.Position.Height+Spacing;
	  }
      }
  }

  return 0;
}

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

METHOD(OM_GET,struct opGet *)

{
  switch (Message->opg_AttrID)
    {
    case BOOPSILibA_LayoutInfo:
      *Message->opg_Storage=(ULONG)&INSTANCEDATA->LayoutInfo;
      return TRUE;

    case BOOPSILibA_Group_Children:
      *Message->opg_Storage=(ULONG)&INSTANCEDATA->Children;
      return TRUE;
    }
  return SUPER_METHOD;
}

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

STATIC_SAVEDS_ASM_A0A1A2(ULONG,Dispatcher,struct IClass *,TheClass,Msg,Message,Object *,TheObject)

{
  switch(Message->MethodID)
    {
      METHOD_DISP(OM_NEW,struct opSet *);
      METHOD_DISP(OM_DISPOSE,Msg);
      METHOD_DISP(OM_ADDMEMBER,struct opMember *);
      METHOD_DISP(OM_REMMEMBER,struct opMember *);
      METHOD_DISP(BOOPSILibM_Layout,struct BOOPSILibP_Layout *);
      METHOD_DISP(BOOPSILibM_Init,struct BOOPSILibP_Init *);
      METHOD_DISP(OM_GET,struct opGet *);
    }
  return SUPER_METHOD;
}

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

struct IClass *CreateBOOPSILibC_Group(void)

{
  if ((BOOPSILibC_Group=MakeClass(NULL,BOOPSI_gadgetclass,NULL,sizeof(struct InstanceData),0))!=NULL)
    {
      BOOPSILibC_Group->cl_Dispatcher.h_Entry=Dispatcher;
    }
  return BOOPSILibC_Group;
}
