/* 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);

/*
** NewList als Makro
*/
/*
#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 NOTHINGFOUND 0
#define STRINGFOUND  1
#define CROSSFOUND   2

#define POOLSIZE     4096
#define TRESHSIZE    1024

#define DEFAULTIN  "musiccatalog.cat"
#define DEFAULTLEFT "CDs"
#define DEFAULTRIGHT "Inhalt"
#define NOMEM        "Not enough memory!\n"

/* Globale Variablen und Strukturen */

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

/* libnix specials */

int __nocommandline = 1;
int __initlibraries = 1;

struct TagItem gg1tags[]={GTLV_Labels,0,GTLV_ShowSelected,TAG_DONE,0};
struct TagItem gg2tags[]={GTLV_Labels,0,GTLV_ReadOnly,TRUE,TAG_DONE,0};

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 *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
 			    };

struct NewGadget gadget1=
			{
			 5,15,200,150,NULL,0,1,PLACETEXT_BELOW,0,0
			};
struct NewGadget gadget2=
			{
			 210,15,200,150,NULL,0,2,PLACETEXT_BELOW,0,0
			};

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,100,
				 WA_MinHeight,70,
				 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("INFILE,LEFTTEXT,RIGHTTEXT",Kommandoparameter,NULL);
	 gadget1.ng_GadgetText=(UBYTE *)Kommandoparameter[1];
	 gadget2.ng_GadgetText=(UBYTE *)Kommandoparameter[2];
	 if((mywindow=OpenWindowTagList(NULL,wintags)))
	  {
	   AdjustGadgets();
	   if((MakeLists()))
	    {
 	     if((MakeGadgets()))
	      {
	       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);
		   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);
		       }
		      break;
		     case IDCMP_NEWSIZE:
		      RemoveGadgets();
		      AdjustGadgets();
		      MakeGadgets();
		     default:
		      break;
		    }
		  }
		}
	ende:
	       RemoveGadgets();
	      }
	     else
	      {
	       ShowMsg("Could not allocate Gadgets.\n");
	      }
	     }
	    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);
  } 
  LibDeletePool(mempool);
 return 0;
}

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

void AdjustGadgets(void)
 {
  gadget1.ng_LeftEdge = mywindow->BorderLeft+5;
  gadget1.ng_TopEdge  = mywindow->BorderTop+5;
  gadget1.ng_Height   = mywindow->Height - mywindow->BorderBottom - 15 - gadget1.ng_TopEdge;
  if(gadget1.ng_Height < 5)
   {
    gadget1.ng_Height = 5;
   }
  gadget2.ng_Height = gadget1.ng_Height;
  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_TopEdge  = gadget1.ng_TopEdge;
  gadget2.ng_Height   = gadget1.ng_Height;
  gadget2.ng_Width    = gadget1.ng_Width;
 }

/* 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;

    if((dummygg=CreateContext(&glistptr)))
     {
      if((gg1ptr=CreateGadgetA(LISTVIEW_KIND,dummygg,&gadget1,gg1tags)))
       {
	if((gg2ptr=CreateGadgetA(LISTVIEW_KIND,gg1ptr,&gadget2,gg2tags)))
	 {
	  ret=TRUE;
	  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(titleheader->mlh_Head == NULL)
   {
    titleheader = NULL;
   }
  return(titleheader);
 }

/* Suche StringB in StringA, gebe Byte des Fundes zurück oder 0. Sucht maximal 100 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);
 }


