
#include <exec/libraries.h>
#include <stdlib.h>
#include <exec/types.h>
#include <proto/gadtools.h>
#include <libraries/gadtools.h>
#include <proto/intuition.h>
#include <intuition/intuition.h>
#include <proto/graphics.h>
#include <graphics/text.h>
#include <string.h>
#include <intuition/gadgetclass.h>

#define GL_VBOX -1
#define GL_HBOX -2
#define CUSTOM_KIND -3

#define MODE_NEW     0
#define MODE_RESIZE  1
#define MODE_REFRESH 2
#define MODE_FREE    3
#define MODE_BACKUP  4
#define MODE_RESTORE 5

#define BOXFLG_CUSTOM   1
#define BOXFLG_STRING   2
#define BOXFLG_INTEGER  4
#define BOXFLG_DISABLED 8
#define BOXFLG_BACKUP   16
#define BOXFLG_INITED   (1<<15)
#define BOXFLG_SKIPREFRESH (1<<14)

#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))

struct GadDim {
   int Kind;
   USHORT Rx,Ry,Cx,Cy,Px,Py,SRx,SRy,SPx,SPy;
   USHORT Flags;
};

struct Box {
   struct GadDim Dim;
   struct Box **Entry;
};

struct WinInfo {
   struct Box *Box;
   struct Gadget **Gadgets;
   struct Gadget *Prev;
   struct Window *Window;
   struct Gadget *GList;
   int FontX,FontY;
   struct TextAttr TextAttr;
   APTR Visual;
   char Mode,Render;
//   USHORT MinX,MinY;
};

struct GadInfo {
   struct GadDim Dim;
   int XSpace,YSpace,GadNum;
   int (*CustomFunc)(struct WinInfo *WInfo,
                     struct NewGadget *NewGad,
                     struct GadInfo *GadInfo,
                     int Left, int Top, int Width, int Height);
   struct TextAttr *TextAttr;
   char *Text;
   int GadgetID;
   ULONG Flags,UserData;
   int (*HookFunc)(struct IntuiMessage *);
   ULONG *Tags;
   ULONG *SaveTags;
   ULONG Code;
   struct Gadget *ThisGad;
};

#define GetString(g) (((struct StringInfo * )g->SpecialInfo)->Buffer)
#define GetNumber(g) (((struct StringInfo * )g->SpecialInfo)->LongInt)
#define GetInfo(g) ((struct GadInfo *)(g->UserData))
#define GetUserData(g) (GetInfo(g)->UserData)

ULONG Modify[]={0,0,GTCB_Checked,GTIN_Number,GTLV_Selected,
                          GTMX_Active,0,GTCY_Active,GTPA_Color,
                          GTSC_Top,0,GTSL_Level,GTST_String,0};

ULONG Default[]={0,0,FALSE,0,~0,0,0,0,1,0,0,0,(ULONG)"",0};

static ULONG *MergeTags(ULONG *oldlist,ULONG *newlist)
{
   ULONG *p;
   if(!oldlist) {
      oldlist=malloc(16*sizeof(ULONG));
      if(!oldlist) return(NULL);
      oldlist[0]=16;
      oldlist[1]=TAG_DONE;
   }

   while(*newlist!=TAG_DONE) {
      switch(*newlist) {
         case TAG_IGNORE:  newlist+=2;
                           break;
         case TAG_MORE:    newlist=(ULONG *)newlist[1];
                           break;
         case TAG_SKIP:    newlist+=2*newlist[1]+2;
                           break;
         default:

                           p=oldlist+1;
            /* passenden Eintrag in der Taglist suchen, bzw Ende der Liste */
                           while(*p!=TAG_DONE && *p!=*newlist) p+=2;
                           if(*p==*newlist) {
                              p[1]=newlist[1]; // alte Daten ueberschreiben
                              newlist+=2;
                           } else {
                              int n=p-oldlist; // Position im Array;

                              if(n>=oldlist[0]-1) { // Array vergrößern
                                 ULONG *mem;
                                 mem=realloc(oldlist,oldlist[0]+16*sizeof(ULONG));
                                 if(!mem) return(NULL);
                                 oldlist=mem;
                                 p=oldlist+n;
                              }

                              *p++=*newlist++;
                              *p++=*newlist++;
                              *p++=TAG_DONE;
                           }
                           break;
      }
   }
   return(oldlist);
}

BOOL Gui_SetGadgetAttrsA(struct Gadget *gad,struct Window *win,
                       struct Requester *req, ULONG *Tag)
{
   ULONG *newtags;
   struct GadInfo *gi;

   gi=GetInfo(gad);

   if(!gi->SaveTags) {
      gi->SaveTags=MergeTags(NULL,gi->Tags);
      if(!gi->SaveTags) return(FALSE);
   }

   newtags=MergeTags(gi->SaveTags,Tag);
   if(!newtags) return(FALSE);
   gi->SaveTags=newtags;
   GT_SetGadgetAttrsA(gad,win,req,(struct TagItem *)Tag);
   return(TRUE);
}
BOOL Gui_SetGadgetAttrs(struct Gadget *gad,struct Window *win,
                       struct Requester *req, ULONG Tag1,...)
{
   return(Gui_SetGadgetAttrsA(gad,win,req,&Tag1));
}

int MakeGTGadget(struct WinInfo *winfo,                              ///
                            struct GadInfo *gad,
                            int left, int top, int width, int height)
{
   struct NewGadget ng;

   if(winfo->Mode==MODE_REFRESH && gad->Dim.Kind>=0) return(0);

   ng.ng_LeftEdge=left + (gad->XSpace>>1);
   ng.ng_TopEdge=top + (gad->YSpace>>1);
   ng.ng_Width=width - gad->XSpace;
   ng.ng_Height=height - gad->YSpace;
   ng.ng_GadgetText=gad->Text;
   ng.ng_TextAttr=gad->TextAttr ? gad->TextAttr : &winfo->TextAttr;
   ng.ng_GadgetID=gad->GadgetID;
   ng.ng_Flags=gad->Flags;
   ng.ng_VisualInfo=winfo->Visual;
   ng.ng_UserData=(void *)gad;

   gad->Dim.Flags|=BOXFLG_INITED;

   if(gad->Dim.Kind>=0) {
      // Falls dies kein erneutes aufsetzen ist: SaveTags neu errechnen
      if(!(winfo->Box->Dim.Flags & BOXFLG_BACKUP) && winfo->Mode==MODE_NEW) {
         ULONG *tags;
         static ULONG modifytag[]={0,0,TAG_DONE};

         if(gad->SaveTags) free(gad->SaveTags);
         gad->SaveTags=NULL;

         if(Modify[gad->Dim.Kind]) {
            modifytag[0]=Modify[gad->Dim.Kind];
            modifytag[1]=Default[gad->Dim.Kind];
            tags=MergeTags(NULL,modifytag);
            if(!tags) return(1);
            gad->SaveTags=tags;
         }
         tags=MergeTags(gad->SaveTags,gad->Tags);
         if(!tags) return(1);
         gad->SaveTags=tags;
      }

      winfo->Prev=gad->ThisGad=winfo->Gadgets[gad->GadNum]=
          CreateGadgetA(gad->Dim.Kind,winfo->Prev,&ng,(struct TagItem *)(gad->SaveTags+1));
      if(winfo->Prev==NULL) return(1); else return(0);
   } else {
      return(gad->CustomFunc(winfo,&ng,gad,left,top,width,height));
   }
} ///
static int MakeGuiGadget(struct WinInfo *winfo,                      ///
                          struct Box *box,
                          int left,int top,int width,int height)
{
   struct Box **p;
   int relx=0,rely=0,absx=0,absy=0;
   int w,h;

   if(box->Dim.Kind>0 || box->Dim.Kind==CUSTOM_KIND)
                         return(MakeGTGadget(winfo,(struct GadInfo *)box,
                                                 left,top,width,height));

   if(!box->Entry) return(0);

   relx=box->Dim.SRx;
   rely=box->Dim.SRy;
   absx=box->Dim.SPx;
   absy=box->Dim.SPy;

   if(!winfo->Render) {
      for(p=box->Entry;*p;p++) {
         if(MakeGuiGadget(winfo,(*p),0,0,0,0)) return(1);
      }
   } else {
      if(box->Dim.Kind==GL_HBOX) {

         width-=absx;
         if(width<0) width=0;

         for(w=0,p=box->Entry;*p;p++) {
            w=width * ((*p)->Dim.Rx)/relx + (*p)->Dim.Px;
            h=height * ((*p)->Dim.Ry)/rely + (*p)->Dim.Py;

            if(MakeGuiGadget(winfo,(*p),left,top,w,h)) return(1);

            left+=w;
         }

      } else {

         height-=absy;
         if(height<0) height=0;

         for(w=0,p=box->Entry;*p;p++) {
            w=width * ((*p)->Dim.Rx)/relx + (*p)->Dim.Px;
            h=height * ((*p)->Dim.Ry)/rely + (*p)->Dim.Py;

            if(MakeGuiGadget(winfo,(*p),left,top,w,h)) return(1);

            top+=h;
         }

      }
   }
   return(0);
}          ///

static void InitGui(struct WinInfo *winfo,struct Box *box)           ///
{
   struct Box **p;
   int relx=0,rely=0,absx=0,absy=0;

   box->Dim.Px+=box->Dim.Cx*winfo->FontX;
   box->Dim.Py+=box->Dim.Cy*winfo->FontY;
   box->Dim.Cx=0;
   box->Dim.Cy=0;

   if(!box->Entry || box->Dim.Kind>0 || box->Dim.Kind==CUSTOM_KIND) {
      return;
   }

   for(p=box->Entry;*p;p++) InitGui(winfo,*p);

   if(box->Dim.Kind==GL_HBOX) {

      for(p=box->Entry;*p;p++) {
         relx+=(*p)->Dim.Rx;
         absx+=(*p)->Dim.Px;

         rely=max(rely,(*p)->Dim.Ry);
         absy=max(absy,(*p)->Dim.Py);
      }
   } else {
      for(p=box->Entry;*p;p++) {
         rely+=(*p)->Dim.Ry;
         absy+=(*p)->Dim.Py;

         relx=max(relx,(*p)->Dim.Rx);
         absx=max(absx,(*p)->Dim.Px);
      }
   }

   if(relx==0) relx=1;
   if(rely==0) rely=1;

   box->Dim.SRx=relx;
   box->Dim.SRy=rely;
   box->Dim.SPx=absx;
   box->Dim.SPy=absy;

   box->Dim.Flags=BOXFLG_INITED;
   for(p=box->Entry;*p;p++) {
      box->Dim.Flags|=(*p)->Dim.Flags;
   }

   return;
}          ///
static int BackupGui(struct WinInfo *winfo,struct Box *box)          ///
{
   struct GadInfo *gad=(struct GadInfo *)box;
   int err=0;

   if(box->Dim.Flags & (BOXFLG_STRING | BOXFLG_INTEGER)) {

      if(box->Dim.Kind==INTEGER_KIND) {
         ULONG tags[]={GTIN_Number,0,TAG_DONE};
         tags[1]=GetNumber(gad->ThisGad);
         MergeTags(gad->SaveTags,tags);
//         gad->Code=GetNumber(gad->ThisGad);
      } else if(box->Dim.Kind==STRING_KIND) {
         ULONG tags[]={GTST_String,0,TAG_DONE};
         if(gad->Code) free((void *)gad->Code);
         gad->Code=(ULONG)strdup(GetString(gad->ThisGad));
         if(!gad->Code) return(1);
         tags[1]=gad->Code;
         MergeTags(gad->SaveTags,tags); // This is always successful, since
                                        // there is always a GTST_String tag
                                        // in the taglist (see MakeGTGadget)
      } else if(box->Dim.Kind==GL_VBOX || box->Dim.Kind==GL_HBOX) {
         struct Box **p;

         if(!box->Entry) return(0);

         for(p=box->Entry; *p; p++) {
            err|=BackupGui(winfo,*p);
         }
      }
   }
   return(err);
} ///
static void FindCustom(struct WinInfo *winfo,struct Box *box)        ///
{
   struct GadInfo *gad=(struct GadInfo *)box;
   static struct NewGadget ng; /* Just to avoid Enforcer Hits */

   if( !(box->Dim.Flags & BOXFLG_CUSTOM)) return;

   if(box->Dim.Kind==CUSTOM_KIND) {
      gad->CustomFunc(winfo,&ng,gad,0,0,0,0);
   } else if(box->Dim.Kind==GL_VBOX || box->Dim.Kind==GL_HBOX) {
      struct Box **p;

      if(box->Dim.Flags & BOXFLG_CUSTOM) {
         if(!box->Entry) return;

         for(p=box->Entry; *p; p++) {
            FindCustom(winfo,*p);
         }
      }
   }
} ///

struct IntuiMessage *Gui_GetIMsg(struct MsgPort *userport,struct IntuiMessage *imsg) ///
{
   struct IntuiMessage *msg;
   int ignore=0;
   struct Gadget *gad;

   if(msg=GT_GetIMsg(userport)) {
      *imsg=*msg;
      GT_ReplyIMsg(msg);

      switch(imsg->Class) {
         case IDCMP_MOUSEMOVE:
         case IDCMP_GADGETUP:
         case IDCMP_GADGETDOWN:
            gad=(struct Gadget *)imsg->IAddress;
            if(gad && gad->GadgetID>=0) {
               struct GadInfo *gi;
               ULONG tags[]={0,0,TAG_DONE};
               gi=GetInfo(gad);

               if(Modify[gi->Dim.Kind]) {
                  tags[0]=Modify[gi->Dim.Kind];
                  tags[1]=imsg->Code;
                  MergeTags(gi->SaveTags,tags); //always successful in this case
               }
               if(GetInfo(gad)->HookFunc) ignore=GetInfo(gad)->HookFunc(imsg);
            }
            break;
      }
      if(!ignore) return(imsg);
   }
   return(NULL);
} ///

void ClearWindow(struct Window *win)
{
   RefreshWindowFrame(win);
   EraseRect(win->RPort,win->BorderLeft,
                        win->BorderTop,
                        win->Width-1-win->BorderRight,
                        win->Height-1-win->BorderBottom);
}

int RenderGui(struct Window *win, struct WinInfo *winfo)             ///
{
   winfo->Window=win;
   winfo->Mode=MODE_NEW;
   winfo->Render=1;

   AskFont(win->RPort,&winfo->TextAttr);

   winfo->FontX = win->RPort->Font->tf_XSize;
   winfo->FontY = win->RPort->Font->tf_YSize;
   winfo->Visual=GetVisualInfoA(win->WScreen,NULL);

   if(!(winfo->Box->Dim.Flags & BOXFLG_INITED)) {
      InitGui(winfo,winfo->Box);
      winfo->Box->Dim.Flags|=BOXFLG_INITED;
   }

   if(!winfo->Visual) goto exit_fail2;

   if(!CreateContext(&winfo->GList)) return(1);

   winfo->Prev=winfo->GList;

   if(MakeGuiGadget(winfo,winfo->Box,win->BorderLeft,win->BorderTop,
                     win->Width - win->BorderLeft - win->BorderRight,
                     win->Height - win->BorderTop - win->BorderBottom))
                                                             goto exit_fail3;
   AddGList(win,winfo->GList,-1,-1,NULL);
   RefreshGList(winfo->GList,win,NULL,-1);
   GT_RefreshWindow(win,NULL);

   if(winfo->Box->Dim.Flags & BOXFLG_BACKUP) {
      winfo->Mode=MODE_RESTORE;
      FindCustom(winfo,winfo->Box);
      winfo->Box->Dim.Flags &= ~BOXFLG_BACKUP;
   }

   return(0);

exit_fail3:
   if(winfo->GList) FreeGadgets(winfo->GList);
   FreeVisualInfo(winfo->Visual);
exit_fail2:
   return(1);
}  ///
int ResizeGui(struct WinInfo *winfo)                                 ///
{
   struct Window *win=winfo->Window;

   winfo->Box->Dim.Flags|=BOXFLG_SKIPREFRESH;
   winfo->Mode=MODE_BACKUP;
   winfo->Render=0;

   ClearWindow(win);

   FindCustom(winfo,winfo->Box); // Backup od customgadgets

   BackupGui(winfo,winfo->Box);

   winfo->Mode=MODE_RESIZE;
   winfo->Render=1;

   if(winfo->GList) {
      RemoveGList(win,winfo->GList,-1);
      FreeGadgets(winfo->GList);
   }
   winfo->GList=NULL;

   ClearWindow(win);

   if(!CreateContext(&winfo->GList)) return(1);
   winfo->Prev=winfo->GList;

   if(MakeGuiGadget(winfo,winfo->Box,win->BorderLeft,win->BorderTop,
                     win->Width - win->BorderLeft - win->BorderRight,
                     win->Height - win->BorderTop - win->BorderBottom))
                                                                  return(2);

   AddGList(win,winfo->GList,-1,-1,NULL);
   RefreshGList(winfo->GList,win,NULL,-1);
   GT_RefreshWindow(win,NULL);
   winfo->Mode=MODE_RESTORE;
   winfo->Render=0;
   FindCustom(winfo,winfo->Box);
   return(0);
}  ///
int RefreshGui(struct WinInfo *winfo)                                ///
{
   struct Window *win=winfo->Window;

   if(winfo->Box->Dim.Flags&BOXFLG_SKIPREFRESH) {
      winfo->Box->Dim.Flags&=~BOXFLG_SKIPREFRESH;
      return(0);
   }

   winfo->Render=1;
   winfo->Mode=MODE_REFRESH;
   if(MakeGuiGadget(winfo,winfo->Box,win->BorderLeft,win->BorderTop,
                    win->Width - win->BorderLeft - win->BorderRight,
                    win->Height - win->BorderTop - win->BorderBottom)) return(1);

   RefreshGList(winfo->GList,win,NULL,-1);
   GT_RefreshWindow(winfo->Window,NULL);
   return(0);
}         ///

static void FreeStopGui(struct WinInfo *winfo,int backup)                       ///
{
   winfo->Render=0;
   if(backup) {
      winfo->Mode=MODE_BACKUP;
      FindCustom(winfo,winfo->Box);
      winfo->Box->Dim.Flags|=BOXFLG_BACKUP;
   }
   winfo->Mode=MODE_FREE;
   FindCustom(winfo,winfo->Box);

   if(winfo->GList) {
      RemoveGList(winfo->Window,winfo->GList,-1);
      FreeGadgets(winfo->GList);
   }
   FreeVisualInfo(winfo->Visual);
} ///
void FreeGui(struct WinInfo *winfo)
{
   FreeStopGui(winfo,0);
}
void StopGui(struct WinInfo *winfo)
{
   FreeStopGui(winfo,1);
}

int RenderSubGui(struct WinInfo *winfo,                              ///
                 int left,int top,int width, int height)
{
   if(!(winfo->Box->Dim.Flags & BOXFLG_INITED)) {
      InitGui(winfo,winfo->Box);
      winfo->Box->Dim.Flags|=BOXFLG_INITED;
   }
   if(MakeGuiGadget(winfo,winfo->Box,left,top,width,height)) return(1);

   return(0);
} ///
int ResizeSubGui(struct WinInfo *winfo,                              ///
                 int left, int top,int width,int height)
{
   if(MakeGuiGadget(winfo,winfo->Box,left,top,width,height)) return(1);

   return(0);
} ///
int RefreshSubGui(struct WinInfo *winfo,int left,int top,int width,int height) ///
{
   winfo->Mode=MODE_REFRESH;
   return(MakeGuiGadget(winfo,winfo->Box,left,top,width,height));
} ///
void FreeSubGui(struct WinInfo *winfo)                               ///
{
   winfo->Mode=MODE_FREE;
   FindCustom(winfo,winfo->Box);
}                                         ///

int SubGui(struct WinInfo *parent, struct WinInfo *winfo,            ///
                  int left,int top,int width, int height)
{
   int ret=0;

   memcpy((void *)(((ULONG)winfo)+2*sizeof(void *)),
          (void *)(((ULONG)parent)+2*sizeof(void *)),
          sizeof(struct WinInfo)-2*sizeof(void *));

   switch(parent->Mode) {

      case MODE_NEW:     ret=RenderSubGui(winfo,left,top,width,height);
                         winfo->Box->Dim.Flags&=~BOXFLG_BACKUP;
                         break;
      case MODE_RESIZE:  ret=ResizeSubGui(winfo,left,top,width,height);
                         winfo->Box->Dim.Flags&=~BOXFLG_BACKUP;
                         break;
      case MODE_REFRESH: ret=RefreshSubGui(winfo,left,top,width,height);
                         break;
      case MODE_FREE:    FreeSubGui(winfo);
                         break;
      case MODE_RESTORE:
                         FindCustom(winfo,winfo->Box);
                         break;
      case MODE_BACKUP:  ret=BackupGui(winfo,winfo->Box);
                         FindCustom(winfo,winfo->Box);
                         winfo->Box->Dim.Flags|=BOXFLG_BACKUP;
                         break;
   }
   parent->Prev=winfo->Prev;
   return(ret);
}  ///
