/*
 * SONIXPEEK.C                              by Eddy Carroll, April 1988
 * ~~~~~~~~~~~                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * Scans one or more Aegis Sonix music files building a list of unique
 * instruments used within those files. At the end, a list of the
 * instruments found is printed (can be redirected to a file if
 * desired).
 *
 * Usage: SonixPeek {-h} {-i} {-n} {-ofile} {-xdirectory} filename ...
 *
 * If filename is a Sonix file, then only that file is scanned. If
 * filename is a directory, then all the files in that directory which
 * contain Sonix files are scanned.
 * 
 * The -h flag suppresses the printing of the header which is normally
 * present at the top of the list of instruments.
 *
 * If the -i (interactive) flag is present, then after each file is
 * found, the user is asked whether it should be included or not when
 * building the list of instruments.
 *
 * If the -o flag is present, then the list of instruments is stored
 * in the specified file, rather than being displayed on the screen.
 *
 * If the -x flag is present, then the output is given in the form
 * of an executable file which consists of a line for each instrument
 * found of the form COPY <instrument> TO <directory>, where <directory>
 * is given immediately after the -x switch.
 *
 * If the -n flag is present, then the list of instruments found in each
 * file, when a directory is being searched, is not printed.
 *
 * DISTRIBUTION
 * I retain copyright to the source and executable code for SonixPeek, but
 * it may be freely redistributed as long as no profit is made from it.
 * 
 * Compiles under Lattice C V4.0
 *
 * Note: Some of this code is messy. Have patience with it :-)
 *
 */

#include <exec/types.h>
#include <libraries/dos.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/intuition.h>
#include <string.h>

/* The following is a quick hack to avoid having to link with lc.lib */
#define strcat(p,q) (strcpy((p)+strlen(p),(q)))

/*
 * New handler for Ctrl-C. Checks if CTRL-C received, and if it has, sets
 * the global CtrlC variable to true.
 */
int CtrlC;
#define chkabort() (CtrlC |= ((SetSignal(0,0) & SIGBREAKF_CTRL_C)))

#define tolower(c) ((c) >= 'A' && (c) <= 'Z' ? (c) + 'a' - 'A' : (c))

#define EOL      '\012'
#define EOF      '\037'
#define TAB      '\011'
#define CR       '\015'

#define MAXINS		1000	/* Only 200 instruments allowed		*/
#define MAXFILENUM	200		/* No more than 200 scores allowed	*/
#define MAXSTRING	256		/* Maximum length of a string		*/

#define INDENT ("    ")		/* Amount to indent instruments by	*/

/* IFF ID definitions */

#define MakeID(a,b,c,d) (long)((long)(a)<<24 | (long)(b)<<16 | (c)<<8 | (d))
#define FORM MakeID('F','O','R','M')
#define SMUS MakeID('S','M','U','S')
#define NAME MakeID('N','A','M','E')
#define SNX1 MakeID('S','N','X','1')
#define INS1 MakeID('I','N','S','1')
#define TRAK MakeID('T','R','A','K')

#define roundup(a) ((a + 1L) & ~1L)

int interactive;		/* TRUE if interactive selection of files			*/
int multi;				/* TRUE if a directory specified instead of file 	*/
int supress;			/* TRUE if listing of instruments is supressed		*/
int noheader;			/* TRUE if no header ouput at start of instr. list	*/
char *dirname;			/* Directory to copy to for -x option				*/
char *suffix[] =
	{".ss", ".instr"};	/* For creating 'copy' batch file					*/

LONG Examine(), ExNext(), Read(), Write();
BPTR Lock(), CurrentDir();

int numins;					/* Current number of instruments	*/
int numfiles;				/* Current number of files 			*/
char *ins[MAXINS];			/* Room for 1000 instruments		*/
char *files[MAXFILENUM];	/* Room for 200 files				*/

BPTR stdin, stdout, stderr, infile, outfile;
struct FileInfoBlock myfib;

/*
 *		The following lets us use our own version of malloc(). The only
 *		additional requirements are that we open intuition.library first,
 *		and call FreeRemember(&memkey,TRUE) just before exiting.
 */
struct Remember *memkey;	/* Used for tracking memory */
#define malloc(n) (AllocRemember(&memkey,n,0L))
struct IntuitionBase *IntuitionBase;

/*
 *		Output string to file. Note that if CTRL-C has been typed,
 *		output is suppressed.
 */

void fprint(f, s)
BPTR f;
char *s;
{
	if (!CtrlC) {
		Write(f, s, strlen(s));
		chkabort();
	}
}

/*
 *		Outputs 3 strings to file f (handy for printing "String" var "String")
 *		The strings are concatenated first to ensure that they are printed
 *		as a single unit, and won't be interrupted by ^C, or even just the
 *		user pressing space, if the output is stdout or stderr.
 */
void fprint3(f,s1,s2,s3)
BPTR f;
char *s1,*s2,*s3;
{
	static char buf[MAXSTRING * 3];
	strcpy(buf,s1);
	strcat(buf,s2);
	strcat(buf,s3);
	fprint(f,buf);
}

/*
 *		print() and print3() are similar to fprint() and fprint3(), but
 *		they output directly to stderr.
 */
#define print(s) fprint(stderr,s)
#define print3(s1,s2,s3) fprint3(stderr,s1,s2,s3)

/*
 *		Standard exit routine for program. Deallocates resources and exits.
 *		If it spots CtrlC has been pressed, it prints an appropriate message.
 */

void abort(code)
int code;	/* Exit code */
{
	if (CtrlC)
		Write(stderr,"^C\n",3);	/* print() won't work when CtrlC is true */
	if (outfile != stdout && outfile != NULL)
		Close(outfile);
	if (stderr)
		Close(stderr);
	if (memkey)
		FreeRemember(&memkey,TRUE);
	if (IntuitionBase) 
		CloseLibrary(IntuitionBase);
	exit(code);
}

/*
 *		Prints help message to standard output
 */
void help()
{
fprint3(stderr,"\n\
SonixPeek Instrument Lister Copyright Eddy Carroll April 1988\n\
\n\
Usage: SonixPeek {-h} {-i} {-n} {-ofile} {-xdirectory} file ...\n\
\n\
File is the Aegis Sonix file for which the instruments are to\n","\
be listed. If it is a directory, then all the Sonix files in\n\
that directory are checked, and a sorted list of the instruments\n\
contained in all of them is prepared. The flags operate as follows:\n\
\n","\
 -h  Suppress output of header at start of instrument listing\n\
\n\
 -i  Ask user whether or not to include each score found\n\
\n\
 -n  Don't list instruments as they are found\n\
\n");fprint(stderr,"\
 -o  Redirect sorted output to named file\n\
\n\
 -x  Format output as an execute file which will copy all the\n\
     instruments found to the named directory\n\
\n");
}

/*** Start of actual program ***/

void main(argc, argv)
int argc;
char *argv[];
{

	void addinstrument(), skip(), dumpins();
	char charin();
	BPTR mylock, oldlock;
	char *fname;

	stdin   = Input();
	stderr  = Open("*",MODE_NEWFILE);
	outfile = stdout = Output();
	infile  = 0;

	if ((IntuitionBase = OpenLibrary("intuition.library",0L)) == NULL)
		abort(99);

	/* Scan command line for possible switches */

	for ( argv++; argc > 1 && argv[0][0] == '-'; argc--,argv++) {
		switch (tolower(argv[0][1])) {

		case 'h':	noheader = 1;
					break;

		case 'i':	interactive = 1;
					break;

		case 'n':   supress = 1;
					break;

		case 'o':	if ((outfile = Open(argv[0]+2,MODE_NEWFILE)) == 0) {
						print3("Can't open file ",argv[0]+2," for output.\n");
						abort(20);
					}
					break;

		case 'x':	dirname = argv[0] + 2;
					break;

		default:	print3("Unknown option ", argv[0], " ignored.\n");
					break;
		}
	}

	/* If missing filename, or filename == '?', print instructions */

	if (argc == 1 || argv[0][0] == '?') {
		help();
		abort(0);
	}
	if (argc > 2)	/* More than one file specified */
		multi = 1;	/* so enable printing of instrument list for each file	*/

	/*
	 *		Now scan each of the files or directories specified, reading
	 *		the instruments from each one, and adding them to the list.
	 */
	for ( ; argc > 1; argc--, argv++) {
		fname = argv[0];
		if ((mylock = Lock(fname, ACCESS_READ)) == 0L) {
			print3("Can't open file ",fname," for input.\n");
			abort(20);
		}
		Examine(mylock,&myfib);
		if (myfib.fib_DirEntryType > 0L) {
			multi = 1;
			oldlock = CurrentDir(mylock);
			while (chkabort(), ExNext(mylock, &myfib) && !CtrlC)
				if (myfib.fib_DirEntryType < 0L)
					scan(&(myfib.fib_FileName), fname); 
			oldlock = CurrentDir(oldlock);
		} else
			scan(fname,NULL);
	}
	dumpins();
	UnLock(mylock);
	abort(0);
}

/*
 *		Scans filename for instruments. If found, they are added to the
 *		list of instruments already in memory. Returns 0 for success, -1
 *		if invalid file. The path parameter, which may be NULL, is the
 *		path to be prefixed to the filename before it is stored in memory.
 *		Note that this may entail adding a trailing '/' to the path.
 */

int scan(filename,path)
char *filename;
char *path;
{
	LONG header[3];
	char instr[50], response[50], *p;
	int plen, foundinstr = 0;

	if ((infile = Open(filename,MODE_OLDFILE)) == 0L) {
		print3("Can't open file ", filename, " for input\n");
		return(-1);
	}

	if (Read(infile,(char *)header,8L) != 8L || header[0] != FORM) {
		Close(infile);
		return(-1);
	}

	if (Read(infile,(char *)header,4L) != 4L || header[0] != SMUS) {
		Close(infile);
		return(-1);
	}

	if (interactive) {
		print3("Include file ", filename, " (CR = no)? ");
		Read(stdin, response, 50L);
		if (tolower(*response) != 'y')
			return(-1);
	} else if (multi && !supress)
		fprint3(stdout, filename, ":\n", "");

	while (chkabort(), Read(infile,(char *)header,8L) == 8L && !CtrlC) {
		if (header[0] != INS1) {
			skip(infile,header[1]);
		} else {
			skip(infile,4L); /* skip position of instrument parameter */
			Read(infile, instr, roundup(header[1]) - 4L);
			instr[header[1] - 4L] = '\0';	/* Null-terminate string */
			addinstrument(instr);
			foundinstr = 1;
		}
	}
	if (multi && !supress)
		fprint(stdout,"\n");
	if (foundinstr) {
		if (path) {
			plen = strlen(path);
			p = malloc(strlen(filename)+plen+2);
			/* Allocate extra byte for zero terminator and for possible '/' */
			strcpy(p, path);
			if (plen && p[plen-1] != ':' && p[plen-1] != '/')
				strcat(p,"/");
			strcat(p,filename);
		} else {
			p = malloc(strlen(filename)+1);
			strcpy(p,filename);
		}
		files[numfiles++] = p;
	}
	Close(infile);
	return(0);
}

void skip(file,size)
BPTR file;
LONG size;
{
	char s[256];
	LONG len = 1L;
	size = roundup(size);
	while (chkabort(), size > 0 && len && !CtrlC) {
		len = Read(file, s, (size > 256 ? 256 : size));
		size -= 256;
	}
}


/*
 *		Adds instrument of length len into list of instruments, but only
 *		if its not already present in the list.
 */

void addinstrument(instr)
char *instr;
{
	int pos, i;
	if ((pos = matchins(instr)) != -1) {
		for (i = numins++; i > pos; i--)
			ins[i] = ins[i-1];
		ins[pos] = malloc(strlen(instr)+1);
		strcpy(ins[pos], instr);
	}
	if (multi && !supress) {
		fprint3(stdout, INDENT, instr, "\n");
	}
}

/*
 *		Compares string p to string s, ignoring case of alpha chars.
 *		Returns -ve if p < s, 0 if p = s, +ve if p > s.
 */

int mystrcmp(p,s)
char *p, *s;
{
	while (*p && *s && tolower(*p) == tolower(*s))
		p++, s++;
	return(tolower(*p) - tolower(*s));
}

/*
 *		Searches instrument array for a match with given instrument. 
 *		Returns -1 if found, else position in array to insert new element.
 */

int matchins(instr)
char *instr;
{
	int i, z;

	for (i = 0; i < numins; i++) {
		if ((z = mystrcmp(instr, ins[i])) <= 0) {
			if (z)
				return(i); /* If less, insert here */
			else
				return(-1); /* If equal, don't insert  */
		}
	}
	return (i); /* Must be at end of list, so return last item */
}

/*
 *		Dumps instrument list to outfile
 */
void dumpins()
{
	int i, j;

	if (numins == 0) {
		print("No instruments found.\n");
		return;
	}

	if (!noheader) {
		fprint(outfile,";\n; Sorted list of instruments\n");
		fprint(outfile,   "; --------------------------\n;\n");
		if (numfiles == 1)
			fprint3(outfile, "; Taken from file ", files[0], "\n");
		else {
			fprint(outfile,"; Taken from these files:\n;\n");
			for (i = 0; i < numfiles && !CtrlC; i++) {
				fprint3(outfile, ";     ", files[i], "\n");
			}
		}
		fprint(outfile,";\n");
	}
	if (dirname)
		fprint(outfile,"failat 30\n");
	for (i = 0; i < numins && !CtrlC; i++) {
		for (j = (dirname != 0); j >= 0 && !CtrlC; j--) {
			if (dirname) {
				fprint3(outfile, "copy ", ins[i], " to ");
				fprint3(outfile, dirname, "\n", "");
			} else
				fprint3(outfile, INDENT, ins[i], "\n");
		}
	}
}
