#include "BOOPSILib.h"

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

#ifndef INTUITION_IMAGECLASS_H
#include <intuition/imageclass.h>
#endif

#include <proto/keymap.h>

#define MYLIB_BOOPSI
#define ROM_VERSION 39

#include "MyLib.h"

#include "StaticSavedsAsmA0A1A2.h"

/****** Root/--background-- **********************************************
*
*	Instances of this class are used as root objects. They act
*	like normal groups, but manage additional information that
*	is required for proper window handling.
*
*	Parent class: BOOPSILibC_Group
*
*************************************************************************/

struct InstanceData
{
  struct TextFont *Font;
  struct Gadget *Gadgets;
  struct Window *Window;
  struct MinList ControlKeyList;
};

struct ControlKeyClient
{
  struct MinNode Node;
  Object *TheObject;
  char Key;
};

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

struct IClass *BOOPSILibC_Root;

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

METHOD(OM_NEW,struct opSet *)

{
  if ((TheObject=(Object *)SUPER_METHOD)!=NULL)
    {
      struct InstanceData *InstanceData;

      ((struct ExtGadget *)TheObject)->Flags|=GFLG_RELSPECIAL;
      InstanceData=INSTANCEDATA;
      InstanceData->Gadgets=NULL;
      InstanceData->Font=NULL;
      NewList((struct List *)&InstanceData->ControlKeyList);
    }
  return (ULONG)TheObject;
}

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

METHOD(OM_DISPOSE,Msg)

{
  struct InstanceData *InstanceData;
  struct ControlKeyClient *Client;

  InstanceData=INSTANCEDATA;
  while ((Client=(struct ControlKeyClient *)RemHead((struct List *)&InstanceData->ControlKeyList))!=NULL)
    {
      FreeMem(Client,sizeof(*Client));
    }
  return SUPER_METHOD;
}

/****** Root/BOOPSILibM_Root_ProcessMessage ******************************
*
*    NAME
*	BOOPSILibM_Root_ProcessMessage -- process IDCMP message
*
*    SYNOPSIS
*	struct BOOPSILibP_Root_ProcessMessage
*	  {
*	    ULONG MethodID;
*	    struct IntuiMessage *Message;
*	  };
*
*	Result: struct IntuiMessage *NewMessage;
*
*    SEE ALSO
*	BOOPSILibM_Root_ReplyMessage
*
*************************************************************************/

METHOD(BOOPSILibM_Root_ProcessMessage,const struct BOOPSILibP_Root_ProcessMessage *)

{
  struct InstanceData *InstanceData;
  struct IntuiMessage *NewMessage;

  InstanceData=INSTANCEDATA;
  NewMessage=Message->Message;
  switch (NewMessage->Class)
    {
    case IDCMP_RAWKEY:
      {
	char Buffer[2];
	struct InputEvent InputEvent;

	InputEvent.ie_Class=IECLASS_RAWKEY;
	InputEvent.ie_SubClass=0;
	InputEvent.ie_Code=NewMessage->Code&~IECODE_UP_PREFIX;
	InputEvent.ie_Qualifier=NewMessage->Qualifier;
	InputEvent.ie_EventAddress=(APTR *)*((ULONG *)NewMessage->IAddress);
	if (MapRawKey(&InputEvent,Buffer,sizeof(Buffer),NULL)==1)
	  {
	    const struct ControlKeyClient *Client;

	    for (Client=(struct ControlKeyClient *)InstanceData->ControlKeyList.mlh_Head;
		 Client->Node.mln_Succ!=NULL;
		 Client=(struct ControlKeyClient *)Client->Node.mln_Succ)
	      {
		if (Client->Key==Buffer[0])
		  {
		    DoGadgetMethod((struct Gadget *)Client->TheObject,InstanceData->Window,NULL,BOOPSILibM_ControlChar,NULL,NewMessage->Code);
		    return DoMethod(TheObject,BOOPSILibM_Root_ReplyMessage,NewMessage);
		  }
	      }
	  }
      }
      break;
    }
  return (ULONG)NewMessage;
}

/****i* Root/BOOPSILibM_Root_AddGadget ***********************************
*
*    NAME
*	BOOPSILibM_Root_AddGadget -- add gadget to window
*
*    SYNOPSIS
*	struct BOOPSILibP_Root_AddGadget
*	  {
*	    ULONG MethodID;
*	    struct Gadget *Gadget;
*	  };
*
*    FUNCTION
*	Add a gadget to the window. Gadget-objects call this method
*	during their BOOPSILibM_Init method.
*
*************************************************************************/

METHOD(BOOPSILibM_Root_AddGadget,const struct BOOPSILibP_Root_AddGadget *)

{
  struct Gadget **Gadget;

  for (Gadget=&INSTANCEDATA->Gadgets; *Gadget!=NULL; Gadget=&(*Gadget)->NextGadget)
    ;
  *Gadget=Message->Gadget;
  return 0;
}

/****** Root/BOOPSILibM_Root_ReplyMessage ********************************
*
*    NAME
*	BOOPSILibM_Root_ReplyMessage -- reply an IDCMP message
*
*    SYNOPSIS
*	struct BOOPSILibP_Root_ReplyMessage
*	  {
*	    ULONG MethodID;
*	    struct IntuiMessage *Message;
*	  };
*
*    FUNCTION
*	Reply a message gotten from BOOPSILibM_Root_ProcessMessage.
*
*************************************************************************/

METHOD(BOOPSILibM_Root_ReplyMessage,struct BOOPSILibP_Root_ReplyMessage *)

{
  struct IntuiMessage *TheMessage;

  if ((TheMessage=Message->Message)!=NULL)
    {
      ReplyMsg(&TheMessage->ExecMessage);
    }
  return (ULONG)NULL;
}

/****** Root/BOOPSILibM_Root_Init ****************************************
*
*    NAME
*	BOOPSILibM_Root_Init -- set up window tags
*
*    SYNOPSIS
*	struct BOOPSILibP_Root_Init
*	  {
*	    ULONG MethodID;
*	    struct TagItem *WindowTags;
*	    WORD SizeY, SizeX;
*	  };
*
*	Result: LONG Error
*
*    FUNCTION
*	This method will update the WindowTags as required.
*	Call this after you have finished setting up your
*	window contents.
*
*	Your WindowTags list MUST contain a WA_PubScreen
*	or WA_CustomScreen tag with a non-NULL screen pointer!
*
*	The following tags will be updated:
*	  WA_MinWidth
*	  WA_MaxWidth
*	  WA_MinHeight
*	  WA_MaxHeight
*	  WA_Gadgets
*	  WA_Flags      (some bits might be changed)
*	  WA_IDCMP	(might get some bits ored in)
*	  WA_Width      (if not between MinWidth and MaxWidth)
*	  WA_Height     (if not between MinHeight and MaxHeight)
*
*	  WA_Left       (center window below pointer if ~0)
*	  WA_Top        (center window below pointer if ~0)
*
*    RESULT
*	Error - 0 for success, else an error code:
*	           BOOPSILIBERROR_SCREENTOSMALL
*	           ERROR_NO_FREE_STORE
*
*    NOTE
*	You should really pass in a WA_IDCMP, for your convenience.
*	If you are using multiple windows, pass in WA_IDCMP to get
*	the required IDCMP bits, then TAG_IGNORE it when opening
*	the window. Pass the ti_Data to ModifyIDCMP().
*
*************************************************************************/

METHOD(BOOPSILibM_Root_Init,const struct BOOPSILibP_Root_Init *)

{
  struct InstanceData *InstanceData;

  struct Screen *Screen;
  struct DrawInfo *DrawInfo;
  LONG Error;

  InstanceData=INSTANCEDATA;

  {
    struct ControlKeyClient *Client;

    while ((Client=(struct ControlKeyClient *)RemHead((struct List *)&InstanceData->ControlKeyList))!=NULL)
      {
	FreeMem(Client,sizeof(*Client));
      }
  }

  Error=0;
  Screen=NULL;
  {
    struct TagItem *Tag, *State;

    State=Message->WindowTags;
    while (Screen==NULL && (Tag=NextTagItem(&State))!=NULL)
      {
	if (Tag->ti_Tag==WA_PubScreen || Tag->ti_Tag==WA_CustomScreen)
	  {
	    Screen=(struct Screen *)Tag->ti_Data;
	    break;
	  }
      }
  }
  assert(Screen!=NULL);
  if ((DrawInfo=GetScreenDrawInfo(Screen))!=NULL)
    {
      struct BOOPSILibS_LayoutInfo *LayoutInfo;
      struct BOOPSILibS_MinMax Width;
      struct BOOPSILibS_MinMax Height;
      WORD t, u;
      struct
	{
	  unsigned int ResizeWidth:1;
	  unsigned int ResizeHeight:1;
	} Flags;

      LayoutInfo=(struct BOOPSILibS_LayoutInfo *)DoMethod(TheObject,BOOPSILibM_Init,TheObject,Screen,DrawInfo,DrawInfo->dri_Font);
      Width=LayoutInfo->Width;
      Height=LayoutInfo->Height;

      Flags.ResizeWidth=(Width.Min!=Width.Max);
      Flags.ResizeHeight=(Height.Min!=Height.Max);

      u=DrawInfo->dri_Font->tf_YSize*DrawInfo->dri_Resolution.X/DrawInfo->dri_Resolution.Y;
      t=DrawInfo->dri_Font->tf_YSize*DrawInfo->dri_Resolution.Y/DrawInfo->dri_Resolution.X;
      if (t<u)
	{
	  u=DrawInfo->dri_Font->tf_YSize;
	}
      else
	{
	  t=DrawInfo->dri_Font->tf_YSize;
	}

      ((struct ExtGadget *)TheObject)->LeftEdge=Screen->WBorLeft+t;
      t=2*t+Screen->WBorRight+Screen->WBorLeft;
      Width.Min+=t;
      Width.Max+=t;
      ((struct ExtGadget *)TheObject)->TopEdge=Screen->WBorTop+u+Screen->Font->ta_YSize+1;
      u+=((struct ExtGadget *)TheObject)->TopEdge+Screen->WBorBottom;
      Height.Min+=u;
      Height.Max+=u;

      if (Screen->Width>=Width.Min && Screen->Height>=Height.Min)
	{
	  ULONG SizingFlags;

	  SizingFlags=0;
	  if (Flags.ResizeWidth || Flags.ResizeHeight)
	    {
	      Object *SizingImage;

	      SizingImage=NewObject(NULL, BOOPSI_sysiclass,
				    SYSIA_DrawInfo, DrawInfo,
				    SYSIA_Which, SIZEIMAGE,
				    SYSIA_Size, ((Screen->Flags & SCREENHIRES) ?
						 ((Screen->ViewPort.Modes & LACE) ? SYSISIZE_HIRES : SYSISIZE_MEDRES) :
						 SYSISIZE_LOWRES),
				    TAG_DONE);
	      if (SizingImage!=NULL)
		{
		  t=Height.Min+((struct Image *)SizingImage)->Height-Screen->Height;
		  if (t<0)
		    {
		      t=((struct Image *)SizingImage)->Height-Screen->WBorBottom;
		      Height.Min+=t;
		      Height.Max+=t;
		      SizingFlags=WFLG_SIZEGADGET | WFLG_SIZEBBOTTOM;
		    }
		  else if (t>0)
		    {
		      t=Width.Min+((struct Image *)SizingImage)->Width-Screen->Width;
		      if (t<0)
			{
			  t=((struct Image *)SizingImage)->Width-Screen->WBorRight;
			  Width.Min+=t;
			  Width.Max+=t;
			  SizingFlags=WFLG_SIZEGADGET | WFLG_SIZEBRIGHT;
			}
		    }
		  DisposeObject(SizingImage);
		}
	      else
		{
		  Error=ERROR_NO_FREE_STORE;
		}
	    }
	  if (Error==0)
	    {
	      struct TagItem *Tag, *State;
	      ULONG IntuiLock;

	      State=Message->WindowTags;
	      while ((Tag=NextTagItem(&State))!=NULL)
		{
		  switch (Tag->ti_Tag)
		    {
		    case WA_MinWidth:
		      Tag->ti_Data=Width.Min;
		      break;

		    case WA_MaxWidth:
		      Tag->ti_Data=Width.Max>0 ? Width.Max : ~0;
		      break;

		    case WA_MinHeight:
		      Tag->ti_Data=Height.Min;
		      break;

		    case WA_MaxHeight:
		      Tag->ti_Data=Height.Max>0 ? Height.Max : ~0;
		      break;

		    case WA_Gadgets:
		      Tag->ti_Data=(ULONG)InstanceData->Gadgets;
		      break;

		    case WA_Flags:
		      Tag->ti_Data|=SizingFlags;
		      break;

		    case WA_Width:
		      if (Tag->ti_Data<Width.Min || (Width.Max>0 && Tag->ti_Data>Width.Max))
			{
			  Tag->ti_Data=Width.Min+(Width.Min*Message->SizeX)/100;
			  if (Width.Max>0 && Tag->ti_Data>Width.Max)
			    {
			      Tag->ti_Data=Width.Max;
			    }
			}
		      break;

		    case WA_Height:
		      if (Tag->ti_Data<Height.Min || (Height.Max>0 && Tag->ti_Data>Height.Max))
			{
			  Tag->ti_Data=Height.Min+(Height.Min*Message->SizeY)/100;
			  if (Height.Max>0 && Tag->ti_Data>Height.Max)
			    {
			      Tag->ti_Data=Height.Max;
			    }
			}
		      break;

		    case WA_IDCMP:
		      {
			if (!IsListEmpty((struct List *)&InstanceData->ControlKeyList))
			  {
			    Tag->ti_Data|=IDCMP_RAWKEY | IDCMP_INACTIVEWINDOW;
			  }
		      }
		      break;
		    }
		}

	      IntuiLock=LockIBase(0);
	      if ((Tag=FindTagItem(WA_Left,Message->WindowTags))!=NULL && Tag->ti_Data==~0)
		{
		  Tag->ti_Data=Screen->MouseX-GetTagData(WA_Width,0,Message->WindowTags)/2;
		}
	      if ((Tag=FindTagItem(WA_Top,Message->WindowTags))!=NULL && Tag->ti_Data==~0)
		{
		  Tag->ti_Data=Screen->MouseY-GetTagData(WA_Height,0,Message->WindowTags)/2;
		}
	      UnlockIBase(IntuiLock);
	    }
	}
      else
	{
	  Error=BOOPSILIBERROR_SCREENTOSMALL;
	}
      FreeScreenDrawInfo(Screen,DrawInfo);
    }
  return (ULONG)Error;
}

/****i* Root/GM_LAYOUT ***************************************************
*
*************************************************************************/

METHOD(GM_LAYOUT,struct gpLayout *)

{
  struct InstanceData *InstanceData;
  struct Window *Window;
  struct BOOPSILibP_Layout Layout;

  Layout.GadgetInfo=Message->gpl_GInfo;
  InstanceData=INSTANCEDATA;
  InstanceData->Window=Window=Layout.GadgetInfo->gi_Window;
  Layout.Position.Left=((struct ExtGadget *)TheObject)->LeftEdge;
  Layout.Position.Top=((struct ExtGadget *)TheObject)->TopEdge;
  Layout.Position.Width=Window->Width-(Layout.Position.Left-Window->BorderLeft)-Window->BorderRight-Layout.Position.Left;
  Layout.Position.Height=Window->Height-(Layout.Position.Top-Window->BorderTop)-Window->BorderBottom-Layout.Position.Top;
  SetAttrs(TheObject,GA_Bounds,&Layout.Position,TAG_DONE);
  Layout.Root=TheObject;
  Layout.MethodID=BOOPSILibM_Layout;
  return DoMethodA(TheObject,&Layout);
}

/****i* Root/BOOPSILibM_Root_RequestKey **********************************
*************************************************************************/

METHOD(BOOPSILibM_Root_RequestKey,struct BOOPSILibP_Root_RequestKey *)

{
  struct ControlKeyClient *Client;

  if ((Client=AllocMem(sizeof(*Client),0))!=NULL)
    {
      Client->Key=Message->Key;
      Client->TheObject=Message->TheObject;
      AddTail((struct List *)&INSTANCEDATA->ControlKeyList,(struct Node *)&Client->Node);
      return TRUE;
    }
  return FALSE;
}

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

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(GM_LAYOUT,struct gpLayout *);
      METHOD_DISP(BOOPSILibM_Root_ProcessMessage,struct BOOPSILibP_Root_ProcessMessage *);
      METHOD_DISP(BOOPSILibM_Root_ReplyMessage,struct BOOPSILibP_Root_ReplyMessage *);
      METHOD_DISP(BOOPSILibM_Root_Init,const struct BOOPSILibP_Root_Init *);
      METHOD_DISP(BOOPSILibM_Root_AddGadget,const struct BOOPSILibP_Root_AddGadget *);
      METHOD_DISP(BOOPSILibM_Root_RequestKey,struct BOOPSILibP_Root_RequestKey *);
    }
  return SUPER_METHOD;
}

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

struct IClass *CreateBOOPSILibC_Root(void)

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