/* JB-CatViewer -- soll die JukeBox Katalogdateien komfortabler anzeigen */

/* INCLUDE */

#include <exec/lists.h>
#include <exec/memory.h>
#include <intuition/intuition.h>
#include <libraries/gadtools.h>
#include <clib/alib_protos.h>
#if defined(_DCC)
  #include <clib/intuition_protos.h>
  #include <clib/graphics_protos.h>
  #include <clib/gadtools_protos.h>
  #include <clib/exec_protos.h>
  #include <clib/dos_protos.h>
#else
  #include <proto/intuition.h>
  #include <proto/graphics.h>
  #if defined(__GNUC__)
    #define GT_SetGadgetAttrsA _GT_SetGadgetAttrsA
  #endif
  #include <proto/gadtools.h>
  #if defined(__GNUC__)
    #undef GT_SetGadgetAttrsA
  #endif
  #include <proto/exec.h>
  #include <proto/dos.h>
#endif
#include <string.h>

struct VolNode {
   struct Node    ExecNode;
   UBYTE          Pad[2];
   struct MinList TitleHeader;
 };

struct FindPara {
   BYTE Success,Pad[3];
   LONG Position;
 };

/* P R O T O T Y P E N */

void AdjustGadgets(void);
int MakeGadgets(void);
void RemoveGadgets(void);
int MakeLists(void);
void CatchNextString(char *,LONG,struct FindPara *);
struct VolNode *MakeVolNode(char *);
struct Node *MakeNode(char *);
struct MinList *TitleHeaderOfNthVolNode(int);
void FindStr(char *,char *,struct FindPara *);
int CompareFrag(char *,char *);
void ShowMsg(STRPTR);
void Search(int,char *);
void SearchMessage(char *,char *);

/*
** NewList als Makro
*/
/* DEFINES */

/*
#define NEWLIST(l) ((l)->lh_Head = (struct Node *)&(l)->lh_Tail, \
                    (l)->lh_Tail = NULL, \
                    (l)->lh_TailPred = (struct Node *)&(l)->lh_Head)
*/ /* deaktiviert, benutzt amiga.lib NewList! */

#define SEARCHLEN 50

#define NOTHINGFOUND 0
#define STRINGFOUND  1
#define CROSSFOUND   2

#define FROMTOP 0
#define CONTINUE 1
#define INIT 2

#define POOLSIZE     4096
#define TRESHSIZE    1024

#define DEFAULTIN  "musiccatalog.cat"
#define DEFAULTLEFT "CD"
#define DEFAULTRIGHT "Titel"
#define DEFAULTSEARCH "weitersuchen"
#define DEFAULTFOUND "Text gefunden!"
#define NOMEM        "Not enough memory!\n"
#define ARGSPATTERN "INFILE,LEFTTEXT,RIGHTTEXT,SEARCHTEXT,FOUNDTEXT"


/* Globale Variablen und Strukturen */

const char VERSION[]="$VER: JukeBox-CatView V1.03 by P. Kursawe 1996";

/* libnix specials */

int __nocommandline = 1;
int __initlibraries = 1;


struct GfxBase *GfxBase = NULL;
struct IntuitionBase *IntuitionBase = NULL;
struct Library *GadToolsBase=NULL;
struct DosLibrary *DOSBase= NULL;
struct Gadget *gg1ptr = NULL;
struct Gadget *gg2ptr = NULL;
struct Gadget *gg4ptr = NULL;
struct Gadget *gg3ptr = NULL;
struct Gadget *glistptr = NULL;
struct Gadget *context = NULL;
struct VisualInfo *vi = NULL;
struct Gadget *dummygg = NULL;
struct Window *mywindow = NULL;

struct MinList Header;

APTR mempool;
struct RDArgs *rdargs = NULL;
ULONG Kommandoparameter[] = {
 			    (ULONG)DEFAULTIN,
 			    (ULONG)DEFAULTLEFT,
 			    (ULONG)DEFAULTRIGHT,
			    (ULONG)DEFAULTSEARCH,
			    (ULONG)DEFAULTFOUND
 			    };


char searchstrbuf[SEARCHLEN] = "#?TEXT#?";

struct TagItem gg1tags[]={GTLV_Labels,0,GTLV_ShowSelected,TAG_DONE,0};
struct TagItem gg2tags[]={GTLV_Labels,0,GTLV_ReadOnly,TRUE,TAG_DONE,0};
struct TagItem gg3tags[]={GTST_String,(ULONG)&searchstrbuf,GTST_MaxChars,SEARCHLEN,TAG_DONE,0};



struct TextAttr normalfont;

struct NewGadget gadget1= 
			{
			 0,0,0,0,NULL,&normalfont,1,PLACETEXT_BELOW,0,0
			};
struct NewGadget gadget2=
			{
			 0,0,0,0,NULL,&normalfont,2,PLACETEXT_BELOW,0,0
			};
struct NewGadget gadget4=
                        {
                         0,0,0,0,NULL,&normalfont,4,PLACETEXT_IN,0,0
                        };
struct NewGadget gadget3 = 
  			 {
  			  0,0,0,0,NULL,&normalfont,3,0,0,0   
   			 };

int FontHeight;

struct StringInfo * strinf; /* Zwischenspeicher wegen Typecasting */

int main()
{
 static struct TagItem wintags[]=
				{
				 WA_Flags,WFLG_SIZEGADGET|WFLG_DRAGBAR|WFLG_DEPTHGADGET|WFLG_CLOSEGADGET|WFLG_ACTIVATE,
				 WA_Left,0,
				 WA_Top,15,
				 WA_MinWidth,400,
				 WA_MinHeight,115,
				 WA_MaxWidth,-1,
				 WA_MaxHeight,-1,
				 WA_Title,(ULONG)"JukeBox Catalog Viewer",
				 WA_Width,640,
				 WA_Height,184,
				 WA_IDCMP,IDCMP_CLOSEWINDOW|LISTVIEWIDCMP|IDCMP_NEWSIZE,
				 TAG_DONE,0
				};
 struct IntuiMessage *imsg;
 struct Gadget *address;
 ULONG imClass;
 UWORD imCode;
 if((mempool=LibCreatePool(MEMF_CLEAR,POOLSIZE,TRESHSIZE)) == NULL)
  return;
 NewList((struct List *)&Header);
 if((DOSBase=(struct DosLibrary*)OpenLibrary("dos.library",37L)))
  {
   if((GfxBase=(struct GfxBase *)OpenLibrary("graphics.library",37L)))
    {
     if((IntuitionBase=(struct IntuitionBase *)OpenLibrary("intuition.library",37L)))
      {
       if((GadToolsBase=OpenLibrary("gadtools.library",37L)))
	{
	 /* hier sind alle Libraries offen */
	 rdargs=ReadArgs(ARGSPATTERN,Kommandoparameter,NULL);
	 gadget1.ng_GadgetText=(UBYTE *)Kommandoparameter[1];
	 gadget2.ng_GadgetText=(UBYTE *)Kommandoparameter[2];
	 gadget4.ng_GadgetText=(UBYTE *)Kommandoparameter[3];
	 if((mywindow=OpenWindowTagList(NULL,wintags)))
	  {
	   /* Verzweifelter Versuch, den richtigen Font zu nehmen */
           FontHeight = mywindow->IFont->tf_YSize;
           normalfont.ta_YSize = FontHeight;
           normalfont.ta_Name = mywindow->IFont->tf_Message.mn_Node.ln_Name;
           normalfont.ta_Style = mywindow->IFont->tf_Style;
           normalfont.ta_Flags = mywindow->IFont->tf_Flags;
           
           /* daran ändert sich nix... */
           gadget4.ng_Height = FontHeight+6;
           gadget3.ng_Height = FontHeight+6;
           gadget1.ng_LeftEdge = mywindow->BorderLeft+5;
           gadget1.ng_TopEdge  = mywindow->BorderTop+5;
           gadget2.ng_TopEdge  = gadget1.ng_TopEdge;
           gadget3.ng_LeftEdge = gadget1.ng_LeftEdge;


	   AdjustGadgets();
	   if((MakeLists()))
	    {
 	     if((MakeGadgets()))
	      {
	       Search(INIT,""); /* in C++ waere das schoener zu loesen... */
	       for(;;)
		{
		 if(Wait(1L<<mywindow->UserPort->mp_SigBit|SIGBREAKF_CTRL_C)&SIGBREAKF_CTRL_C)
		  break;
		 while((imsg=GT_GetIMsg(mywindow->UserPort)) != NULL)
		  {
		   imClass = imsg->Class;
		   imCode  = imsg->Code;
		   address = imsg->IAddress;
		   GT_ReplyIMsg(imsg);
          	   
          	   strinf = (struct StringInfo *)gg3ptr->SpecialInfo; /*Wie sonst soll man den korrekt dereferenzieren? */

		   switch(imClass)
		    {
		     case IDCMP_CLOSEWINDOW:
		      goto ende;
		     case IDCMP_GADGETUP:
		      if(address->GadgetID==1)
		       {
		        gg2tags[0].ti_Data=(ULONG)TitleHeaderOfNthVolNode(imCode);
		        GT_SetGadgetAttrsA(gg2ptr,mywindow,NULL,gg2tags);
		       }
		      if(address->GadgetID==4)
		       {
			Search(CONTINUE,(char *)strinf->Buffer);
		       }
		      if(address->GadgetID==3)
		       {
		        Search(FROMTOP,(char *)strinf->Buffer);
		       }
		      break;
		     case IDCMP_NEWSIZE:
		      strcpy(searchstrbuf,(char *)strinf->Buffer);
		      RemoveGadgets();
		      AdjustGadgets();
		      MakeGadgets();
		     default:
		      break;
		    }
		  }
		}
	ende:
	       RemoveGadgets();
	      }
	     else
	      {
	       ShowMsg("Could not allocate Gadgets.\n");
	      }
	     LibDeletePool(mempool);
	    }
	   else
	    {
	     ShowMsg("Could not build lists\n");
	    }
	   CloseWindow(mywindow);
	  }
	  FreeArgs(rdargs);
	 /* alle Libraries schließen */
	 CloseLibrary(GadToolsBase);
	}
       else
        {
         ShowMsg("Can't open gadtools.library V37.\n");
        }
       CloseLibrary((struct Library *)IntuitionBase);
      }
     else
      {
       ShowMsg("Can't open intuition.library V37.\n");
      }
     CloseLibrary((struct Library *)GfxBase);
    }
   else
    {
     ShowMsg("Can't open graphics.library V37.\n");
    }
   CloseLibrary((struct Library *)DOSBase);
  }
 return 0;
}

/* -------------------------------------- */
/*            UNTERROUTINEN               */
/* -------------------------------------- */

void AdjustGadgets(void)
 {
  gadget1.ng_Height   = mywindow->Height - mywindow->BorderBottom - 2*FontHeight - 16 - gadget1.ng_TopEdge;
  if(gadget1.ng_Height < 5)
   {
    gadget1.ng_Height = 5;
   }
  gadget1.ng_Width    = ( (mywindow->Width - mywindow->BorderLeft - mywindow-> BorderRight - 15) / 2);
   if(gadget1.ng_Width < 10)
    {
     gadget1.ng_Width = 10;
    }

  gadget2.ng_LeftEdge = gadget1.ng_Width + gadget1.ng_LeftEdge + 5;
  gadget2.ng_Height   = gadget1.ng_Height;
  gadget2.ng_Width    = gadget1.ng_Width;

  gadget4.ng_TopEdge = gadget1.ng_TopEdge + gadget1.ng_Height + FontHeight + 5;
  gadget4.ng_Width = 200; /* sollte reichen...*/
  gadget4.ng_LeftEdge = gadget2.ng_LeftEdge + gadget2.ng_Width - gadget4.ng_Width;

  gadget3.ng_TopEdge = gadget4.ng_TopEdge;
  gadget3.ng_Width = gadget2.ng_LeftEdge + gadget2.ng_Width - gadget4.ng_Width - 5 - gadget3.ng_LeftEdge;
 }

/* Muß hier Gadgets belegen und einbinden. */

int MakeGadgets(void)
 {

  int ret=FALSE;

  if((vi=GetVisualInfoA(mywindow->WScreen,NULL)))
   {

    gadget1.ng_VisualInfo = vi;
    gadget2.ng_VisualInfo = vi;
    gadget4.ng_VisualInfo = vi;
    gadget3.ng_VisualInfo = vi;

    if((dummygg=CreateContext(&glistptr)))
     {
      if((gg1ptr=CreateGadgetA(LISTVIEW_KIND,dummygg,&gadget1,gg1tags)))
       {
	if((gg2ptr=CreateGadgetA(LISTVIEW_KIND,gg1ptr,&gadget2,gg2tags)))
	 {
	  if((gg4ptr=CreateGadgetA(BUTTON_KIND,gg2ptr,&gadget4,NULL)))
	   {
	    if((gg3ptr=CreateGadgetA(STRING_KIND,gg4ptr,&gadget3,gg3tags)))
	     {
	      ret=TRUE;
	      strinf = (struct StringInfo *)gg3ptr->SpecialInfo;
	      strcpy((char *)strinf->Buffer,searchstrbuf);
	      EraseRect(mywindow->RPort,mywindow->BorderLeft,mywindow->BorderTop,mywindow->Width-mywindow->BorderRight-1,mywindow->Height-mywindow->BorderBottom-1);
	      AddGList(mywindow,glistptr,-1,-1,0);
	      RefreshWindowFrame(mywindow);
	      GT_RefreshWindow(mywindow,0);
	     }
           }
	 }
	else
	 {
	  FreeGadgets(glistptr);
	  glistptr = NULL;
	 }
       }
     }
    else
     {
      FreeVisualInfo(vi);
     }
   }
  return(ret);
 }

void RemoveGadgets(void)
 {
  if((glistptr))
   {
    RemoveGList(mywindow,glistptr,-1);
    FreeGadgets(glistptr);
    FreeVisualInfo(vi);
   }
 }

int MakeLists(void)
 {
  struct FileInfoBlock *fib = NULL;
  struct VolNode *voln=NULL;
  struct Node *node=NULL;
  struct FindPara Paras;
  BPTR filehandle = NULL;
  BYTE *buffer;
  int ret = FALSE;
  LONG pos=0;
  Paras.Success = CROSSFOUND; Paras.Position = 0;

  if((fib=AllocDosObject(DOS_FIB,NULL)))
   {
    if((filehandle=Open((char *)Kommandoparameter[0],MODE_OLDFILE)))
     {
      if(DOSTRUE==ExamineFH(filehandle,fib))
       {
        if((buffer=LibAllocPooled(mempool,fib->fib_Size)))
         {
           Read(filehandle,buffer,fib->fib_Size);
          /* Eigentliche Bearbeitung der Textdatei */
	   gg1tags[0].ti_Data=(ULONG)&Header;
	  while((Paras.Success != NOTHINGFOUND)&&((int)pos < (int)buffer + (int)fib->fib_Size))
	   {
            pos=pos+Paras.Position;
	    switch(Paras.Success)
	     {
	      case STRINGFOUND:
	       pos=Paras.Position;
	       if((voln))
	        {
	         if((node=MakeNode(&buffer[pos])))
	          {
	           AddTail((struct List *)&voln->TitleHeader,node);
	          }
                }
	       pos=pos+strlen(&buffer[pos]);
	       break;
	      case CROSSFOUND:
	       pos = pos + Paras.Position;
	       CatchNextString(buffer,pos,&Paras);
	       if(Paras.Success == STRINGFOUND)
	        {
	         pos=Paras.Position;
	         if((voln=MakeVolNode(&buffer[pos])))
	          {
	           AddTail((struct List *)&Header,&voln->ExecNode);
	           ret=TRUE;
	          }
	         pos=pos+strlen(&buffer[pos]);
	        }
	       break;
	      default:
	       break;
	     }
            CatchNextString(buffer,pos,&Paras);
           }
          /* fertig.... */
	  LibFreePooled(mempool,buffer,fib->fib_Size);
         }
        else
         {
          ShowMsg(NOMEM);
         }
       }
      else
       {
        ShowMsg("Examine failed.\n");
       }
      Close(filehandle);
     }
    else
     {
      ShowMsg("Could not open \"");
      ShowMsg((char *)Kommandoparameter[0]);
      ShowMsg("\".\n");
     }
    FreeDosObject(DOS_FIB,fib);
   }
  else
   {
    ShowMsg("Could not allocate FileInfoBlock.\n");
   }
  return(ret);
 }

/* Sucht einen String */

void CatchNextString(char *Puffer,LONG position,struct FindPara *parameter)
 {
  LONG posbak=0;
  FindStr(&Puffer[position],">=\"",parameter);
  if(parameter->Success == STRINGFOUND)
   {
    parameter->Position=parameter->Position+3;
    position= position + parameter->Position;
    FindStr(&Puffer[position],"\"",parameter);
    if(parameter->Success != NOTHINGFOUND)
     {
      while(parameter->Success==CROSSFOUND)
       {
        posbak=parameter->Position+posbak;
        FindStr(&Puffer[posbak+position],"\"",parameter);
       }
      Puffer[position + parameter->Position+posbak]=0;
      parameter->Position=position;
     }
    else
     {
      parameter->Success=NOTHINGFOUND;
     }
   }
 }

/* Erzeugt Nodes mit Eintrag fuer Liste der Titel */

struct VolNode *MakeVolNode(char string[])
 {
  struct VolNode *mem;

  if((mem=LibAllocPooled(mempool,sizeof(struct VolNode)+strlen(string)+1)))
   {
    mem->ExecNode.ln_Name=(char *)(mem+1);
    NewList((struct List *)&mem->TitleHeader);
    strcpy((char *)(mem+1),string);
   }
  else
   {
    ShowMsg(NOMEM);
   }
  return mem;
 }

/* Erzeugt stinknormale Nodes (mit Name dahinter, natürlich! */

struct Node *MakeNode(char string[])
 {
  struct Node *mem;

  if((mem=LibAllocPooled(mempool,sizeof(struct Node)+strlen(string)+1)))
   {
    mem->ln_Name=(char *)(mem+1);
    strcpy((char *)(mem+1),string);
   }
  else
   {
    ShowMsg(NOMEM);
   }
  return(mem);
 }

/* Gibt einen Zeiger auf den TitleHeader der n-ten VolNode zurück, NULL, falls leer! */

struct MinList *TitleHeaderOfNthVolNode(int Number)
 {
  struct MinList *titleheader = NULL;
  struct VolNode *voln = NULL;
  int IsNumber = 0;
  voln = (struct VolNode *)Header.mlh_Head;
  for(;IsNumber < Number;IsNumber++)
   {
    voln = (struct VolNode *)voln->ExecNode.ln_Succ;
   }
  titleheader=&voln->TitleHeader;
  if((struct MinList *)titleheader->mlh_TailPred == titleheader)
   {
    titleheader = NULL;
   }
  return(titleheader);
 }

/* Suche StringB in StringA, gebe Byte des Fundes zurück oder 0. Sucht maximal 5000 Bytes lang! */
/* Gibt Erfolgsmeldung in der Struktur, wenn Kreuz zeigt Position 1 hinter Kreuz! */

void FindStr(char *StringA,char *StringB,struct FindPara *result)
 {
  LONG ret = 0;

  while(ret < 5000)
   {
    if(StringA[ret]=='#')
     {
      result->Success = CROSSFOUND;
      result->Position = ret+1;
      return;
     }
    if(StringA[ret]==StringB[0])
     {
      if(CompareFrag(&StringA[ret],StringB)==TRUE)
       {
        result->Position = ret;
        result->Success = STRINGFOUND;
        return;
       }
     }
     ret++;
   }
   result->Success = NOTHINGFOUND;
   return;
 }

/* Überprüft, ob Frag der Anfang von Text ist. (Bsp: Te in Test, nicht aber Te in DerTest) */

int CompareFrag(char *Text,char *Frag)
 {
  LONG cnt = 0;
  while(Frag[cnt] != 0)
   {
    if(Frag[cnt]!=Text[cnt])
     {
      return(FALSE);
     }
    cnt++;
   }
  return(TRUE);
 }

/* gibt nur ne Meldung aus ;) */

void ShowMsg(STRPTR msg)
 {
  PutStr(msg);
 }


/* durchsucht die liste, gibt selbsttaetig Erfolgsmeldung */

void Search(int task,char * texttofind)
 {
  
  static struct VolNode * currentvol;
  static struct VolNode * lastfoundvol;
  static int volnodecounter, nodecounter;
  static struct Node * currentnode;
  static char patternbuf[SEARCHLEN*2+2];
  
  if(ParsePatternNoCase(texttofind,patternbuf,SEARCHLEN*2+2) == -1)
   {
    return;
   }

 switch(task)
  {
   case INIT:
     volnodecounter = 0;
     nodecounter = 0;
     currentnode = NULL;
     currentvol = (struct VolNode *)Header.mlh_Head;
     lastfoundvol = NULL;
     return;
    break;
   case CONTINUE:
    lastfoundvol = currentvol;
    break;
   case FROMTOP:
     currentnode = 0;
     lastfoundvol = 0;
     currentvol = (struct VolNode *)Header.mlh_Head;
     volnodecounter = 0;
     nodecounter = 0;
    break;
  }
 
 while(currentvol != (struct VolNode *)&Header.mlh_Tail)
  {
   /* Volumes scannen */
   if((MatchPatternNoCase(patternbuf,(char *)(currentvol+1))!=NULL) && (lastfoundvol != currentvol))
    {
     SearchMessage((char *)(currentvol+1),"---");
     return;
    }
   if(currentvol->TitleHeader.mlh_TailPred != (struct MinNode *)&currentvol->TitleHeader)
    {
     /* Titeleintraege scannen */

    if(currentnode == 0)
     {
      currentnode = (struct Node *)currentvol->TitleHeader.mlh_Head;
     }
     
     while(currentnode != (struct Node *)&currentvol->TitleHeader.mlh_Tail)
      {
       if(MatchPatternNoCase(patternbuf,(char*)(currentnode+1))!=NULL)
        {
         SearchMessage((char *)(currentvol+1),(char *)(currentnode+1));
         currentnode = currentnode->ln_Succ;
         nodecounter++;
         return;
        }
       nodecounter++;
       currentnode = currentnode->ln_Succ;
      }
   currentnode = 0;
   nodecounter = 0;
   volnodecounter++;
     
    }
   currentvol = (struct VolNode *)currentvol->ExecNode.ln_Succ;
  }
  DisplayBeep(mywindow->WScreen);
 }

void SearchMessage(char * voltext,char * nodetext)
 {
  static struct EasyStruct myeasystruct = {
   sizeof(struct EasyStruct),0,0,"%s: %s\n%s: %s","OK"
   };
  static char * myarglist[4];
  myeasystruct.es_Title = (UBYTE *)Kommandoparameter[4];
  myarglist[0] = (char *)Kommandoparameter[1];
  myarglist[1] = voltext;
  myarglist[2] = (char *)Kommandoparameter[2];
  myarglist[3] = nodetext;  
  EasyRequestArgs(mywindow,&myeasystruct,NULL,myarglist);
 }