#include <stdio.h>
#include <stdlib.h>

extern unsigned long coreleft();
unsigned long TotalMemory;

unsigned PrtPage,PrtLine;
char *Header1,*Header2;
char MainHeader[200];
unsigned PrtPitch;

#include "CASFRAME.INC"

enum NoiseRedType {DolbyC, None, DolbyB, DolbyD};

struct DateStruct {
      unsigned Day:5;
      unsigned Month:4;
      unsigned Year:7;
};

extern char MusicType[32][10];

struct ConfigStruct {
      unsigned UseFilter:1;
      unsigned SongSearch:1;
      unsigned Confirmation:1;
      unsigned CompressedPrint:1;
      unsigned AutoStandardize:1;
      unsigned QualityLabel:1;
      unsigned QualityList:1;
      char InputName[80];
      char FileName[80];
      char EditCommand[80];
      int OutDev;
} Config={1,1,0,1,1,1,0,"Cas.txt","Cas.cas","ED",0};

struct FilterStruct {
       char *Name;
       char *Artist[10];
       char *Album[10];
       char *Title[10];
       unsigned FirstCas[10],LastCas[10];
       unsigned Marked:2;
       unsigned Volumes:2;
       struct DateStruct BeforeDate,AfterDate;
       enum NoiseRedType NR;
       unsigned long MusicType[10];
} *Filter=NULL;

/************** DATA STRUCTRURES **************************/

struct SongStruct {
      struct SongStruct *Next;               /* Next song */
      struct SideStruct *Side;               /* Album where song is contained */
      char *Artist;
      char *Title;
      unsigned Duration;                     /* Duration in seconds */
      unsigned long Music;                   /* Music type for song */
      unsigned RecSide:1;                    /* Recorded on Side 0=A 1=B */
      unsigned Mark:1;                       /* Song is marked */
};

struct SideStruct {
      struct CassetteStruct *Cassette;
      char Side;                             /* Character indicating side of cassette */
      char *Artist;
      char *Album;
      char *Volume;                          /* Volumename in case of an artist-collection */
      unsigned Collection:1;                 /* Album contains songs of more than one artist */
      unsigned long Music;                   /* Music type for whole side (album) */
      struct DateStruct Date;                /* Recording/Purchase date */
      enum NoiseRedType NR;                  /* Noise reduction */
      struct SongStruct *FirstSong;          /* Pointer to first song on cassette */
};

struct CassetteStruct {
      struct CassetteStruct *Next;           /* Next cassette */
      unsigned Number:12;                    /* Number of cassette (0..999=CAS 1000...1999=LP 2000...2999=CD) */
      unsigned Format:3;                     /* Media format (1=Two albums/cassette 2=LP 3=One album/cassette 4=CD) */
      unsigned Mark:1;                       /* Cassette marked for special purpose */
      unsigned Brand:10;                     /* Cassette brand or LP/CD label */
      struct SideStruct *SideA,*SideB;       /* Sides A & B of LP/cassette or contents of CD */
} *FirstCassette,*LastCassette=NULL;

FILE *PrtFile;
FILE *InputFile;
FILE *OutputFile;

unsigned CassetteCount=0;
unsigned SongCount=0;

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

extern struct OutParmStruct {
      char *Name;
      char *Device;
      unsigned Width,Height;
      unsigned Top,Bottom;
      unsigned Left,Right;
      char *Condens;
      char *Elite;
      char *Pica;
      char *NLQ;
      char *Draft;
      char *Large[2];
      char *LineSpace[2];
      char *Eject;
      char *Reset;
      int Pause;
} OutParm[];

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

char *ShowDate(struct DateStruct *Date) {
      static char Buffer[20];
      static char *Months[]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};

      if (Date->Day && Date->Month)
            sprintf(Buffer,"%d-%s-%02d",Date->Day,Months[Date->Month-1],Date->Year);
      else
            strcpy(Buffer,"No date");

      return Buffer;
}

char *ShowNoiseRed(enum NoiseRedType NR) {
      switch (NR) {
            case DolbyB:return "DolbyB";
            case DolbyC:return "DolbyC";
            case DolbyD:return "DolbyD";
            default:return "None";
      }
}

char *ShowCasNr(struct CassetteStruct *Cassette,char Side) {
      static char Buffer[10];

      if (Cassette->Number>=2000)
       sprintf(Buffer,"CD%03u",Cassette->Number-2000);
      else
      if (Cassette->Number>=1000)
      sprintf(Buffer,"LP%03u%c",Cassette->Number-1000,Side);
      else if (Side)
      sprintf(Buffer,"%03u%c",Cassette->Number,Side);
      else
      sprintf(Buffer,"%03u",Cassette->Number,Side);

      return Buffer;
}

char *ShowMusic(unsigned long Music) {
      static char Buffer[64];
      unsigned W;

      if (!Music)
         return "Unknown";

      Buffer[0]='\0';
      for (W=0; W<32; ++W)
            if (Music & (1L<<W)) {
                  strncat(Buffer,MusicType[W],sizeof Buffer);
                  strncat(Buffer," ",sizeof Buffer);
            }
      return Buffer;
}

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

int Confirm(char *String,int Default) {
	char Choice;
	
	printf("%s (%c/%c)? ",String,Default ? 'Y' : 'y',Default ? 'n' : 'N');
	rewind(stdin);
	do {
		Choice=toupper(getchar());
		if (Choice==13)
			Choice=Default ? 'Y' : 'N';
	} while (Choice!='Y' && Choice!='N');
	
	return Choice=='Y';
}

void Error(int Code) {
      switch (Code) {
            case  1:printf("ERROR: End-Of-File or No: expected\n"); Wait(); break;
            case  2:printf("WARNING: Invalid cassette descriptor format -> skipped\n"); break;
            case  3:printf("WARNING: Invalid date format -> date skipped\n"); break;
            case  4:printf("ERROR: Database is empty\n"); Wait(); break;
            case  5:printf("FATAL: Not enough memory for operation\n"); Wait(); break;
            case  6:printf("WARNING: Invalid musictype flag -> ignored\n"); break;
            case  7:printf("ERROR: Invalid selection\n"); Wait(); break;
            case  8:printf("ERROR: Outputfile cannot be opened\n"); Wait(); break;
            case 10:printf("ERROR: Inputfile does not exist\n"); Wait(); break;
	    case 11:printf("WARNING: Invalid flag specified -> skipped\n"); break;
            case 15:printf("ERROR: Searchkey not found\n"); Wait(); break;
            case 20:printf("WARNING: No filter specifications defined\n"); Wait(); break;
      }
}

char *wildcmp1(char *Pattern,char *String) {
  while (*Pattern && *Pattern!='*' && *Pattern!='?' && *String && toupper(*Pattern)==toupper(*String)) {
     ++Pattern;
     ++String;
  }
  return (*Pattern=='*' || *Pattern=='?' || !*Pattern ? String : NULL);
}

char *wildcmp2(char *Pattern,char *String) {
  char *NewString;

  while (*String && !(NewString=wildcmp1(Pattern,String))) ++String;
  return (*String ? NewString : NULL);
}

int wildcmp(char *Pattern,char *String) {
  while (*Pattern) {
    if (*Pattern=='*' && !*(Pattern+1))
      return 1;
    else if (*Pattern=='*') {
      while (*Pattern=='*') ++Pattern;
      String=wildcmp2(Pattern,String);
      if (!String) return 0;
      while (*Pattern && *Pattern!='*' && *Pattern!='?') ++Pattern;
    }
    else if (*Pattern=='?') {
      ++Pattern;
      if (*String) ++String; else return 0;
    }
    else {
      String=wildcmp1(Pattern,String);
      if (!String) return 0;
      while (*Pattern && *Pattern!='*' && *Pattern!='?') ++Pattern;
    }
  }
  return !*Pattern && !*String;
}

void Standardize(char *String) {
  int Capitalize=1;
  int W;
  static char *Separators=" .()!?/";
  static char *Word[]={"the","or","in","at","a","an","I","II","III","IV","V","VI","VII","VIII","IX","X",
		       "XI","XII","XIII","XIV","XV","it","of","from","for","to",NULL};

  while (String && *String) {
    while (*String && strchr(Separators,*String)) {
      if (*String!=' ') Capitalize=1;
      ++String;
    }
    for (W=0; Word[W]; ++W)
      if (!strnicmp(String,Word[W],strlen(Word[W])) && (strchr(Separators,*(String+strlen(Word[W]))) || !*(String+strlen(Word[W])))) {
	strncpy(String,Word[W],strlen(Word[W]));
        if (Capitalize) *String=toupper(*String);
        String+=strlen(Word[W]);
        break;
      }
    if (!Word[W]) {
      *String=toupper(*String);
      ++String;
      while (*String && !strchr(Separators,*String)) {
	*String=tolower(*String);
	++String;
      }
    }
    Capitalize=0;
  }
}

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

void ShowInfo() {
     BuildScreen("System status/Configuration");
     printf("[%c] Filter defined        Cassettes:          %4u\n",Filter ? 'X' : '.',CassetteCount);
     printf("[%c] Filter in use         Songs:             %5u\n",Config.UseFilter ? 'X' : '.',SongCount);
     printf("[%c] Search songs in album\n",Config.SongSearch ? 'X' : '.');
     printf("[%c] Confirmation          Free memory:  %10lu\n",Config.Confirmation ? 'X' : '.',(unsigned long) coreleft());
     printf("[%c] Auto standardize      Total memory: %10lu\n\n",Config.AutoStandardize ? 'X' : '.',TotalMemory);
     printf("Import/export file: [%s]\n",Config.InputName);
     printf("Load/Save file:     [%s]\n\n",Config.FileName);
     printf("Output device:      [%s]\n",OutParm[Config.OutDev].Name);
     printf("Output port:        [%s]\n\n",OutParm[Config.OutDev].Device);
     printf("[%c] Compressed print\n",Config.CompressedPrint ? 'X' : '.');
     printf("[%c] Use NLQ for labelprinting\n",Config.QualityLabel ? 'X' : '.');
     printf("[%c] Use NLQ for reports\n\n\n",Config.QualityList ? 'X' : '.');
     Wait();
}

void ChangeConfig() {
     FILE *ConfigFile;
     char Buffer[80];

     BuildScreen("Change configuration options");
     Config.UseFilter=Confirm("Enable filtering for batchfunctions",Config.UseFilter);
     Config.SongSearch=Confirm("Will filtering select cassettes containing specified songs",Config.SongSearch);
     Config.Confirmation=Confirm("\nConfirm critical user input",Config.Confirmation);
     Config.CompressedPrint=Confirm("\nGenerate reports with more lines per inch",Config.CompressedPrint);
     Config.QualityLabel=Confirm("Shall cassetteindexlabels be printed in Near Letter Quality",Config.QualityLabel);
     Config.QualityList=Confirm("Use NLQ printing while generating reports",Config.QualityList);
     Config.AutoStandardize=Confirm("\nAutostandardize cassetteinfo when exporting/modifying",Config.AutoStandardize); 

     printf("\nDefault file name for import/export functions (%s):\n",Config.InputName);
     gets(Buffer);
     if (!Buffer[0])
       strcpy(Config.InputName,Buffer);
       
     printf("\nEnter new database name (%s):\n",Config.FileName);
     gets(Buffer);
     if (!Buffer[0])
       strcpy(Config.FileName,Buffer);

     printf("\nCommand to invoke editor (%s):\n",Config.EditCommand);
     gets(Buffer);
     if (!Buffer[0])
       strcpy(Config.EditCommand,Buffer);

     ConfigFile=fopen("CAS.CFG","wb");
     if (ConfigFile) {
	fwrite(&Config,sizeof Config,1,ConfigFile);
	fclose(ConfigFile);
     }
     else
        Error(5);

     Wait();
}

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

#include "CASFILT.INC"
#include "CASDISP.INC"

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

void PrintCassetteLabels() {
      struct CassetteStruct *Cassette=FirstCassette;

      while (!Filter) {
         BuildScreen("Print labels");
         Error(20);
         DefineFilter(&Filter);
      }
      BuildScreen("Print labels");

      while (Cassette) {
        if (CheckCassette(Cassette,Filter)) {
           printf("Cassette %d...\r",Cassette->Number);
           PrintLabel(Cassette);
        }
        Cassette=Cassette->Next;
      }
}

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

void BatchMark(unsigned Value) {
 struct CassetteStruct *Cassette=FirstCassette;
 struct SongStruct *Song;

 while (Cassette) {
   Cassette->Mark=Value;
   if (Cassette->SideA) {
     Song=Cassette->SideA->FirstSong;
     while (Song) {
       Song->Mark=Value;
       Song=Song->Next;
     }
   }
   if (Cassette->SideB) {
     Song=Cassette->SideB->FirstSong;
     while (Song) {
       Song->Mark=Value;
       Song=Song->Next;
     }
   }
 Cassette=Cassette->Next;
 }
}	

void BatchStandardize(struct FilterStruct *Filter) {
 struct CassetteStruct *Cassette=FirstCassette;

 if (Config.Confirmation && !Confirm("Do you want to standardize marked cassettes",1))
  return;

 printf("Standardizing...");
 while (Cassette) {
  if (Cassette->Mark) StandardizeCassette(Cassette);
  Cassette=Cassette->Next;
 }
}	

void BatchDelete(struct FilterStruct *Filter) {
 struct CassetteStruct *Cassette=FirstCassette;

 if (Config.Confirmation && !Confirm("Are you sure to delete marked cassettes",0))
  return;

 printf("Deleting...");
 while (Cassette) {
  if (Cassette->Mark) DeleteCassette(Cassette);
  Cassette=Cassette->Next;
 }
}	

void BatchFilteredMark(struct FilterStruct *Filter,unsigned Value) {
 struct CassetteStruct *Cassette=FirstCassette;
 struct SongStruct *Song;
 unsigned SongMark=0,CassetteMark=0;

 printf("Marking...");
 while (Cassette) {
  if (CheckCassette(Cassette,Filter)) {Cassette->Mark=Value; ++CassetteMark;}
  if (Cassette->SideA) {
    Song=Cassette->SideA->FirstSong;
    while (Song) {
      if (CheckSong(Song,Filter)) {Song->Mark=Value; ++SongMark;}
      Song=Song->Next;
    }
  }
  if (Cassette->SideB) {
    Song=Cassette->SideB->FirstSong;
    while (Song) {
      if (CheckSong(Song,Filter)) {Song->Mark=Value; ++SongMark;}
      Song=Song->Next;
    }
  }
  Cassette=Cassette->Next;
 }
 printf("\r%d Songs %smarked (out of %d)\n%d Cassettes %smarked (out of %d)\n",SongMark,Value ? "" : "un",SongCount,CassetteMark,Value ? "" : "un",CassetteCount);
 Wait();
}	

void BatchEdit(struct FilterStruct *Filter) {
      char Buffer[80];	
	
      if (Config.Confirmation && !Confirm("This function will remove all previously defined marks, continue",1))
        return;

      BuildScreen("Batch edit");

      printf("Marking original numbers...\n");
      BatchMark(0);
      BatchFilteredMark(Filter,1);      

      InputFile=fopen("TEMPFILE.$$$","wt");
      if (!InputFile) {
            Error(10);
            return;
      }

      printf("Exporting...\n");
      if (!ExportFile(Filter)) {
	printf("Calling editor...\n");
	
        sprintf(Buffer,"%s TEMPFILE.$$$",Config.EditCommand);
        system(Buffer);
	
        InputFile=freopen("TEMPFILE.$$$","rt",InputFile);
        if (!InputFile) {
            Error(10);
            return;
        }

	if (Config.Confirmation && Confirm("Do you want to make these changes permanent",1)) {
          printf("Importing...\n");
          if (!ImportFile()) {
             printf("\nDone.\n\n");
	     printf("Note that if you changed cassette-numbers, the old cassettes\n");
	     printf("will still exist. To remove the original cassettes use the batch-delete option.\n\n");
             Wait();
          }
	}
      }
      fclose(InputFile);
}

void BatchFunctions() {
	char Choice;
	static struct FilterStruct *Filter=NULL;
	
	do {
		BuildScreen("Batch functions");
		printf("1 - Unmark all items\n");
		printf("2 - Mark all items\n");
		printf("3 - Unmark all selected items\n");
		printf("4 - Mark all selected items\n\n");
		printf("5 - Delete MARKED cassettes\n");
		printf("6 - Standardize MARKED cassettes\n\n");
		printf("7 - Edit selected cassettes\n\n");
		printf("9 - Show filter specifications\n\n");
		printf("0 - Exit\n\n");
		printf("Your choice: ");
		rewind(stdin);
		Choice=toupper(getchar());
		
		if (Choice!='0' && Choice!='5' && Choice!='6' && Choice!='1' && Choice!='2' && Choice!='9') {
		   DefineFilter(&Filter);
		   FilterInfo(&Filter);
		   if (!Filter) continue;
		}
		
		printf("\nBusy: ");		   
		switch (Choice) {
			case '1':BatchMark(0); break;
			case '2':BatchMark(1); break;
			case '3':BatchFilteredMark(Filter,0); break;
			case '4':BatchFilteredMark(Filter,1); break;
			case '5':BatchDelete(Filter); break;
			case '6':BatchStandardize(Filter); break;
			case '7':BatchEdit(Filter); break;
			case '9':FilterInfo(&Filter); break;
		}
	} while (Choice!='0');
}

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

int Cmp(int Value) {
      return (Value<0 ? -1 : Value>0 ? 1 : 0);
}

int LCmp(long Value) {
      return (Value<0L ? -1 : Value>0L ? 1 : 0);
}

int Sort_S_CN(struct SongStruct **First, struct SongStruct **Second) {
      return Cmp(((*First)->Side->Cassette->Number-(*Second)->Side->Cassette->Number)*10+
                 Cmp((*First)->Side->Side-(*Second)->Side->Side));
}

int Sort_S_ST(struct SongStruct **First, struct SongStruct **Second) {
      return stricmp((*First)->Title,(*Second)->Title);
}

int Sort_S_AR(struct SongStruct **First, struct SongStruct **Second) {
      return Cmp(Cmp(stricmp((*First)->Artist ? (*First)->Artist : (*First)->Side->Artist,
                         (*Second)->Artist ? (*Second)->Artist : (*Second)->Side->Artist))*10+
                 Cmp(stricmp((*First)->Title,(*Second)->Title)));
}

int Sort_S_AL(struct SongStruct **First, struct SongStruct **Second) {
      return Cmp(Cmp(stricmp((*First)->Side->Album,(*Second)->Side->Album))*10+
                 Cmp(stricmp((*First)->Title,(*Second)->Title)));
}

int Sort_S_MST(struct SongStruct **First, struct SongStruct **Second) {
      return Cmp(LCmp((*First)->Music-(*Second)->Music)*10+
                 Cmp(stricmp((*First)->Title,(*Second)->Title)));
}

int Sort_S_MAR(struct SongStruct **First, struct SongStruct **Second) {
      return Cmp(LCmp((*First)->Music-(*Second)->Music)*100+
                 Cmp(stricmp((*First)->Artist ? (*First)->Artist : (*First)->Side->Artist,
                             (*Second)->Artist ? (*Second)->Artist : (*Second)->Side->Artist))*10+
                 Cmp(stricmp((*First)->Title,(*Second)->Title)));
}

int Sort_S_MAL(struct SongStruct **First, struct SongStruct **Second) {
      return Cmp(LCmp((*First)->Music-(*Second)->Music)*100+
                 Cmp(stricmp((*First)->Side->Album,(*Second)->Side->Album))*10+
                 Cmp(stricmp((*First)->Title,(*Second)->Title)));
}

void SongList() {
      static int ListFormat=1;
      struct CassetteStruct *Cassette=FirstCassette;
      struct SongStruct **Song,*CurrentSong;
      unsigned W=0;
      char Choice;

      BuildScreen("List songs");

      if (!SongCount) {
            Error(4);
            return;
      }

      Song=calloc(SongCount,sizeof (struct SongStruct *));
      if (!Song) {
            Error(5);
            return;
      }

      while (Cassette) {
            if (Cassette->SideA) {
                  CurrentSong=Cassette->SideA->FirstSong;
                  while (CurrentSong) {
                        Song[W++]=CurrentSong;
                        CurrentSong=CurrentSong->Next;
                  }
            }
            if (Cassette->SideB) {
                  CurrentSong=Cassette->SideB->FirstSong;
                  while (CurrentSong) {
                        Song[W++]=CurrentSong;
                        CurrentSong=CurrentSong->Next;
                  }
            }
            Cassette=Cassette->Next;
      }

      printf("1 - Sort on cassette number\n");
      printf("2 - Sort on songtitle\n");
      printf("3 - Sort on artist\n");
      printf("4 - Sort on album\n");
      printf("5 - Sort on music type and songtitle\n");
      printf("6 - Sort on music type and artist\n");
      printf("7 - Sort on music type and album\n");
      printf("0 - Don't sort (default)\n\n");

      printf("Your choice: ");
      rewind(stdin);
      Choice=getchar();

      printf("Sorting...\n");

      switch (Choice) {
            case '1':qsort((char *) Song,SongCount,sizeof (struct SongStruct *),Sort_S_CN); break;
            case '2':qsort((char *) Song,SongCount,sizeof (struct SongStruct *),Sort_S_ST); break;
            case '3':qsort((char *) Song,SongCount,sizeof (struct SongStruct *),Sort_S_AR); break;
            case '4':qsort((char *) Song,SongCount,sizeof (struct SongStruct *),Sort_S_AL); break;
            case '5':qsort((char *) Song,SongCount,sizeof (struct SongStruct *),Sort_S_MST); break;
            case '6':qsort((char *) Song,SongCount,sizeof (struct SongStruct *),Sort_S_MAR); break;
            case '7':qsort((char *) Song,SongCount,sizeof (struct SongStruct *),Sort_S_MAL); break;
      }

      BuildScreen("List songs");

      printf("Format: 1 - Title/Artist/Date/Cassette/Music\n");
      printf("        2 - Artist/Album/Title/Cassette/Music\n");
      printf("        3 - Title/Artist/Album/Volume/Music\n");
      printf("        9 - Previous used format\n");
      printf("        0 - EXIT\n\n");

      printf("Your choice: ");
      rewind(stdin);
      Choice=getchar();
      
      if (Choice=='0') {
	free(Song);
	return;
      }

      printf("Printing...\n");

      if (Choice>='1' && Choice<='3') ListFormat=Choice-'0';

      sprintf(MainHeader,"\"%s\"      SONGLIST      %u songs  %u cassettes",Config.FileName,SongCount,CassetteCount);

      switch (ListFormat) {
            case 1:PrintInit("SONG TITLE:__________________________________ ARTIST:__________________ CAS:  DATE:____ MUSIC:_____________",
                             "_____________________________________________ _________________________ _____ _________ ___________________");
                   break;
            case 2:PrintInit("ARTIST:__________________ ALBUM:___________________ SONG TITLE:__________________________________ CAS:  MUSIC:______________",
                             "_________________________ _________________________ _____________________________________________ _____ ____________________");
                   break;
            case 3:PrintInit("SONG TITLE:__________________________________ ARTIST:__________________ ALBUM:___________________ VOLUME:___ CAS:  MUSIC:_______________",
                             "_____________________________________________ _________________________ _________________________ __________ _____ _____________________");
                   break;
      }

      for (W=0; W<SongCount; ++W)
            if (Song[W] && (!Config.UseFilter || CheckSong(Song[W],Filter))) {
                  PrintLine();
                  switch (ListFormat) {
                        case 1:fprintf(OutputFile,"%-45s %-25s %-5s %9s %s\n",
                                     Song[W]->Title,
                                     Song[W]->Artist ? Song[W]->Artist : Song[W]->Side->Artist,
                                     ShowCasNr(Song[W]->Side->Cassette,Song[W]->RecSide ? 'A' : 'B'),
                                     ShowDate(&Song[W]->Side->Date),
                                     ShowMusic(Song[W]->Music));
                              break;
                        case 2:fprintf(OutputFile,"%-25s %-25s %-45s %-5s %s\n",
                                     Song[W]->Artist ? Song[W]->Artist : Song[W]->Side->Artist,
                                     Song[W]->Side->Album,
                                     Song[W]->Title,
                                     ShowCasNr(Song[W]->Side->Cassette,Song[W]->RecSide ? 'A' : 'B'),
                                     ShowMusic(Song[W]->Music));
                              break;
                        case 3:fprintf(OutputFile,"%-45s %-25s %-25s %-10s %-5s %s\n",
                                     Song[W]->Title,
                                     Song[W]->Artist ? Song[W]->Artist : Song[W]->Side->Artist,
                                     Song[W]->Side->Album,
                                     Song[W]->Side->Volume ? Song[W]->Side->Volume : "",
                                     ShowCasNr(Song[W]->Side->Cassette,Song[W]->RecSide ? 'A' : 'B'),
                                     ShowMusic(Song[W]->Music));
                              break;
                  }
            }
      PrintExit();
      free(Song);
      printf("Done.\n");
      Wait();
}

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

int Sort_A_CN(struct SideStruct **First, struct SideStruct **Second) {
      return Cmp(Cmp((*First)->Cassette->Number-(*Second)->Cassette->Number)*10+
                 Cmp((*First)->Side-(*Second)->Side));
}

int Sort_A_AR(struct SideStruct **First, struct SideStruct **Second) {
      return Cmp(Cmp(stricmp((*First)->Artist,(*Second)->Artist))*100+
                 Cmp(stricmp((*First)->Album,(*Second)->Album))*10+
                 Cmp(stricmp((*First)->Volume,(*Second)->Volume)));
}

int Sort_A_AL(struct SideStruct **First, struct SideStruct **Second) {
      return Cmp(Cmp(stricmp((*First)->Album,(*Second)->Album))*10+
                 Cmp(stricmp((*First)->Volume,(*Second)->Volume)));
}

int Sort_A_MAR(struct SideStruct **First, struct SideStruct **Second) {
      return Cmp(LCmp((*First)->Music-(*Second)->Music)*1000+
                 Cmp(stricmp((*First)->Artist,(*Second)->Artist))*100+
                 Cmp(stricmp((*First)->Album,(*Second)->Album))*10+
                 Cmp(stricmp((*First)->Volume,(*Second)->Volume)));
}

int Sort_A_MAL(struct SideStruct **First, struct SideStruct **Second) {
      return Cmp(LCmp((*First)->Music-(*Second)->Music)*100+
                 Cmp(stricmp((*First)->Album,(*Second)->Album))*10+
                 Cmp(stricmp((*First)->Volume,(*Second)->Volume)));
}

void AlbumList() {
      static int ListFormat=1;
      struct CassetteStruct *Cassette=FirstCassette;
      struct SideStruct **Album;
      unsigned W=0;
      unsigned AlbumCount=0;
      char Choice;

      Album=calloc(CassetteCount*2,sizeof (struct SideStruct *));
      if (!Album) {
            printf("FATAL: Not enough memory for operation\n");
            getchar();
            return;
      }

      BuildScreen("List albums");

      while (Cassette) {
            if (Cassette->SideA)
                  Album[AlbumCount++]=Cassette->SideA;
            if (Cassette->SideB)
                  Album[AlbumCount++]=Cassette->SideB;
            Cassette=Cassette->Next;
      }

      printf("1 - Sort on cassette number\n");
      printf("2 - Sort on artist\n");
      printf("3 - Sort on album\n");
      printf("4 - Sort on music type and artist\n");
      printf("5 - Sort on music type and album\n");
      printf("0 - Don't sort (default)\n\n");

      printf("Your choice: ");
      rewind(stdin); 
      Choice=getchar();

      printf("Sorting...\n");

      switch (Choice) {
            case '1':qsort((char *) Album,AlbumCount,sizeof (struct SideStruct *),Sort_A_CN); break;
            case '2':qsort((char *) Album,AlbumCount,sizeof (struct SideStruct *),Sort_A_AR); break;
            case '3':qsort((char *) Album,AlbumCount,sizeof (struct SideStruct *),Sort_A_AL); break;
            case '4':qsort((char *) Album,AlbumCount,sizeof (struct SideStruct *),Sort_A_MAR); break;
            case '5':qsort((char *) Album,AlbumCount,sizeof (struct SideStruct *),Sort_A_MAL); break;
      }

      BuildScreen("List albums");

      printf("Format: 1 - Artist/Album/Date/Cassette/Music\n");
      printf("        2 - Album/Volume/Artist/Cassette/Music\n");
      printf("        9 - Previous used format\n");
      printf("        0 - EXIT\n\n");

      printf("Your choice: ");
      rewind(stdin);
      Choice=getchar();
      
      if (Choice=='0') {
	free(Album);
	return;
      }

      printf("Printing...\n");

      if (Choice>='1' && Choice<='2') ListFormat=Choice-'0';

      sprintf(MainHeader,"\"%s\"      ALBUMLIST      %u songs  %u albums  %u cassettes",Config.FileName,SongCount,AlbumCount,CassetteCount);

      switch (ListFormat) {
            case 1:PrintInit("ARTIST:_______________________ ALBUM:________________________ DATE:____ CAS:  MUSIC:_____________",
                             "______________________________ ______________________________ _________ _____ ___________________");
                   break;
            case 2:PrintInit("ALBUM:________________________ VOLUME:___ ARTIST:_______________________ CAS:  MUSIC:______________",
                             "______________________________ __________ ______________________________ _____ ____________________");
                   break;
      }

      for (W=0; W<AlbumCount; ++W)
            if (Album[W] && (!Config.UseFilter || CheckAlbum(Album[W],Filter))) {
                  PrintLine();
                  switch (ListFormat) {
                        case 1:fprintf(OutputFile,"%-30s %-30s %9s %-5s %s\n",
                                     Album[W]->Artist ? Album[W]->Artist : "COLLECTION",
                                     Album[W]->Album,
                                     ShowDate(&Album[W]->Date),
                                     ShowCasNr(Album[W]->Cassette,'\0'),
                                     ShowMusic(Album[W]->Music));
                              break;
                        case 2:fprintf(OutputFile,"%-30s %-10s %-30s %-5s %s\n",
                                     Album[W]->Album,
                                     Album[W]->Volume,
                                     Album[W]->Artist ? Album[W]->Artist : "COLLECTION",
                                     ShowCasNr(Album[W]->Cassette,'\0'),
                                     ShowMusic(Album[W]->Music));
                              break;
                  }
            }
      PrintExit();
      free(Album);
      printf("Done.\n");
      Wait();
}

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

void Import() {
      char InputName[80];

      BuildScreen("Import textfile");
      printf("Import filename (null for \"%s\"): ",Config.InputName);
      rewind(stdin);
      gets(InputName);
      if (!InputName[0])
	strcpy(InputName,Config.InputName);

      InputFile=fopen(InputName,"rt");
      if (!InputFile) {
            Error(10);
            return;
      }

      printf("Importing...\n");
      if (!ImportFile()) {
         printf("\nDone.\n");
         Wait();
      }
      fclose(InputFile);
      strcpy(Config.InputName,InputName);
}

void Export() {
      char InputName[80];

      BuildScreen("Export textfile");
      printf("Export filename (null for \"%s\"): ",Config.InputName);
      rewind(stdin);
      gets(InputName);
      if (!InputName[0])
	strcpy(InputName,Config.InputName);

      InputFile=fopen(InputName,"wt");
      if (!InputFile) {
            Error(10);
            return;
      }

      printf("Exporting...\n");
      if (!ExportFile(Filter)) {
         printf("\nDone.\n");
         Wait();
      }
      fclose(InputFile);
      strcpy(Config.InputName,InputName);
}

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

void WriteString(char *S,FILE *File) {
      unsigned L=0;

      if (S) {
            L=strlen(S)+1;
            fwrite((char *) &L,sizeof L,1,File);
            fwrite(S,L,1,File);
      }
      else
            fwrite((char *) &L,sizeof L,1,File);
}

char *ReadString(FILE *File) {
      unsigned L;
      char *S=NULL;

      fread((char *) &L,sizeof L,1,File);
      if (L)
	if (S=malloc(L))
	  fread(S,L,1,File);
	else
	  fseek(File,(long) L,1);

      return S;
}

void ClearFile() {
      struct SongStruct *Song;
      struct SideStruct *Side;
      struct CassetteStruct *Cassette=FirstCassette,*OldCassette;

      while (Cassette) {
            if (Side=Cassette->SideA) {
                  free(Side->Artist);
                  free(Side->Album);
                  free(Side->Volume);
                  if (Song=Side->FirstSong) {
                        while (Song) {
                              free(Song->Artist);
                              free(Song->Title);
                              free(Song);
                              Song=Song->Next;
                        }
                  free(Side);
                  }
            }
            if (Side=Cassette->SideB) {
                  free(Side->Artist);
                  free(Side->Album);
                  free(Side->Volume);
                  if (Song=Side->FirstSong) {
                        while (Song) {
                              free(Song->Artist);
                              free(Song->Title);
                              free(Song);
                              Song=Song->Next;
                        }
                  }
                  free(Side);
            }
            OldCassette=Cassette;
            Cassette=Cassette->Next;
            free(OldCassette);
      }
      FirstCassette=LastCassette=NULL;
      CassetteCount=SongCount=0;
}


int WriteFile() {
      FILE *Output;
      struct SongStruct *Song;
      struct SideStruct *Side;
      struct CassetteStruct *Cassette=FirstCassette;

      Output=fopen(Config.FileName,"wb");
      if (!Output) {
            printf("\n");
            Error(8);
            return 1;
      }

      while (Cassette) {
            fwrite((char *) Cassette,sizeof (struct CassetteStruct),1,Output);
            if (Side=Cassette->SideA) {
                  fwrite((char *) Side,sizeof (struct SideStruct),1,Output);
                  WriteString(Side->Artist,Output);
                  WriteString(Side->Album,Output);
                  WriteString(Side->Volume,Output);
                  if (Song=Side->FirstSong) {
                        while (Song) {
                              fwrite((char *) Song,sizeof (struct SongStruct),1,Output);
                              WriteString(Song->Artist,Output);
                              WriteString(Song->Title,Output);
                              Song=Song->Next;
                        }
                  }
            }
            if (Side=Cassette->SideB) {
                  fwrite((char *) Side,sizeof (struct SideStruct),1,Output);
                  WriteString(Side->Artist,Output);
                  WriteString(Side->Album,Output);
                  WriteString(Side->Volume,Output);
                  if (Song=Side->FirstSong) {
                        while (Song) {
                              fwrite((char *) Song,sizeof (struct SongStruct),1,Output);
                              WriteString(Song->Artist,Output);
                              WriteString(Song->Title,Output);
                              Song=Song->Next;
                        }
                  }
            }
            Cassette=Cassette->Next;
      }

      fclose(Output);
      return 0;
}

int ReadFile() {
      FILE *Input;
      unsigned long FileSize;
      struct SongStruct *Song;
      struct SideStruct *Side;

      Input=fopen(Config.FileName,"rb");
      if (!Input) {
            printf("\n");
            Error(10);
            return 1;
      }

      fseek(Input,0L,2);
      FileSize=ftell(Input);
      fseek(Input,0L,0);

      while (ftell(Input)<FileSize) {
	    if (coreleft()<4000L) {
                  printf("\n");
                  Error(5);
                  return 1;
            }
            if (!LastCassette)
                  FirstCassette=LastCassette=malloc(sizeof (struct CassetteStruct));
            else
                  LastCassette=(LastCassette->Next=malloc(sizeof (struct CassetteStruct)));
            ++CassetteCount;
            fread((char *) LastCassette,sizeof (struct CassetteStruct),1,Input);
            if (LastCassette->SideA) {
                  Side=(LastCassette->SideA=malloc(sizeof (struct SideStruct)));
                  fread((char *) Side,sizeof (struct SideStruct),1,Input);
                  Side->Cassette=LastCassette;
                  Side->Artist=ReadString(Input);
                  Side->Album=ReadString(Input);
                  Side->Volume=ReadString(Input);
                  if (Side->FirstSong) {
                        Song=NULL;
                        do {
                              if (Song)
                                     Song=(Song->Next=malloc(sizeof (struct SongStruct)));
                              else
                                     Song=(Side->FirstSong=malloc(sizeof (struct SongStruct)));
                              fread((char *) Song,sizeof (struct SongStruct),1,Input);
                              ++SongCount;
                              Song->Side=Side;
                              Song->Artist=ReadString(Input);
                              Song->Title=ReadString(Input);
                        } while (Song->Next);
                  }
            }
            if (LastCassette->SideB) {
                  Side=(LastCassette->SideB=malloc(sizeof (struct SideStruct)));
                  fread((char *) Side,sizeof (struct SideStruct),1,Input);
                  Side->Cassette=LastCassette;
                  Side->Artist=ReadString(Input);
                  Side->Album=ReadString(Input);
                  Side->Volume=ReadString(Input);
                  if (Side->FirstSong) {
                        Song=NULL;
                        do {
                              if (Song)
                                     Song=(Song->Next=malloc(sizeof (struct SongStruct)));
                              else
                                     Song=(Side->FirstSong=malloc(sizeof (struct SongStruct)));
                              fread((char *) Song,sizeof (struct SongStruct),1,Input);
                              ++SongCount;
                              Song->Side=Side;
                              Song->Artist=ReadString(Input);
                              Song->Title=ReadString(Input);
                        } while (Song->Next);
                  }
            }
      }
      fclose(Input);

      return 0;
}

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

void ChangeDevice() {
      int W,W1;

      BuildScreen("Change device");
      printf("Change device %s to:\n\n",OutParm[Config.OutDev].Name);
      for (W=0; OutParm[W].Name; ++W)
         printf("%2d - %s (%s)\n",W+1,OutParm[W].Name,OutParm[W].Device);
      printf(" 0 - EXIT\n");

      printf("\nChange to: ");
      scanf("%d",&W1);
      
      if (!W1) return;
      
      if (W1<1 || W1>W) {
         Error(7);
         return;
      }

      Config.OutDev=(--W1);

      printf("\nDone.\n");
      Wait();
}

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

void main() {
      char Choice;

      InitPrg();

      TotalMemory=coreleft();

      BuildScreen("Initialisation");
      printf("Using the file \"%s\"\n\n",Config.FileName);

      printf("Reading configuration files...");
      Init();
      printf("Done.\n");

      printf("Reading datafile...");
      if (!ReadFile()) {
         printf("Done.\n");
         printf("%d Songs on %d cassettes.\n\n",SongCount,CassetteCount);
         printf("Press return...");
         getchar();
      }

      while (1) {
            BuildScreen("Main menu");
            printf("LA - List albums                  LC - List cassettes\n");
            printf("LS - List songs                   LF - List filtered items\n");
            printf("P  - Print cassette labels        LM - List marked items\n");
            printf("D  - Display cassettes            B  - Batch functions\n\n");

            printf("SD - Set output device            SI - Show info\n");
            printf("SC - Set configuration options\n");
            printf("SF - Set filters\n\n");

            printf("FS - Save database                FC - Clear database\n");
            printf("FL - Load database\n");
            printf("FI - Import from textfile\n");
            printf("FE - Export to textfile\n\n");

            printf("Q  - Quit\n\n");

            printf("Your choice: ");
            Choice=toupper(getchar());
            printf("\n");

            switch (Choice) {
                  case 'P':
                        PrintCassetteLabels();
                        break;
                  case 'B':
                        BatchFunctions();
                        break;
                  case 'D':
                        CassetteDisplay();
                        break;
                  case 'L':
                        Choice=toupper(getchar());
                        switch (Choice) {
                              case 'F':
                                     ListFiltered(Filter);
                                     break;
                              case 'M':
                                     ListMarked();
                                     break;
                              case 'A':
                                     AlbumList();
                                     break;
                              case 'S':
                                     SongList();
                                     break;
                        }
                        break;
                  case 'S':
                        Choice=toupper(getchar());
                        switch (Choice) {
                              case 'D':
                                     ChangeDevice();
                                     break;
                              case 'F':
			      	     DefineFilter(&Filter);
				     FilterInfo(&Filter);
                                     break;
                              case 'I':
                                     ShowInfo();
                                     break;
                              case 'C':
			             ChangeConfig();
                                     ShowInfo();
                                     break;
                        }
                        break;
                  case 'F':
                        Choice=toupper(getchar());
                        switch (Choice) {
                              case 'C':
                                     if (Config.Confirmation && !Confirm("Clear database",0))
				       break;
                                     ClearFile();
                                     break;
                              case 'S':
                                     WriteFile();
                                     break;
                              case 'L':
                                     if (FirstCassette && Confirm("Clear database first",1))
				       ClearFile();
                                     ReadFile();
                                     break;
                              case 'I':
                                     Import();
                                     break;
                              case 'E':
				     Export();
                                     break;
                        }
                        break;
                  case 'Q':
		        if (Config.Confirmation && !Confirm("Quit Cas:Prg",1))
			  break;
                        exit(0);

            }

            rewind(stdin);
      }
}

