/* files.c */

/* songs management, mainly some iterators
 */
 
/* 
 * $Author: Espie $
 * $Date: 91/05/16 15:05:01 $
 * $Revision: 1.8 $
 * $Log:	files.c,v $
 * Revision 1.8  91/05/16  15:05:01  Espie
 * *** empty log message ***
 * 
 * Revision 1.7  91/05/12  19:53:05  Espie
 * Corrected the non-freed icons problem.
 * 
 * Revision 1.6  91/05/12  15:56:53  Espie
 * Bugs mostly corrected.
 * 
 * Revision 1.5  91/05/11  14:58:02  Espie
 * Corrected an auto-cleanup problem.
 * 
 * Revision 1.4  91/05/10  00:04:19  Espie
 * Added albums support. Directories missing, not sure
 * I will add it...
 * 
 * Revision 1.3  91/05/09  17:35:17  Espie
 * Support for appwindow, mk_wbarg optimization.
 * 
 * Revision 1.2  91/05/08  18:33:46  Espie
 * Added check_abort() for loooong runs.
 * Added support for arp pattern-matching... need checking that arp is around.
 * Does enter subdirectories too !
 * 
 * Revision 1.1  91/05/08  15:50:32  Espie
 * Initial revision
 * 
 *
 */
 
#include <exec/types.h>
#include <dos/dos.h>
#include <exec/memory.h>
#include <workbench/startup.h>
#include <workbench/workbench.h>
#include <string.h>
#include <stddef.h>
#include <custom/cleanup.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/icon.h>
#include <stdio.h>

#include <libraries/arpbase.h>
#include <proto/arp.h>

#include "song.h"
#include "public_play.h"

void *malloc();
	
#include "proto.h"

LOCAL struct WBStartup *msg;
LOCAL char **tools;

/***
 *
 *		Tooltypes management
 *
 ***/

/* get_arg(tools, tooltype, def): reads the tooltype in the tools array,
 * as a numerical argument, with def as a default value.
 */

int get_arg(char **tools, char *tooltype, int def)
	{
	int val;
	char *string = NULL;
		if (tools)
			string = FindToolType(tools, tooltype);
		if (string && sscanf(string, "%d", &val) == 1)
			return val;
		else
			return def;
	}

/* same thing with default tools */

int do_get_arg(char *tooltype, int def)
	{
		return get_arg(tools, tooltype, def);
	}
	

/* map_arg: gives a numerical argument from a limited set
 * of strings values
 */
int map_arg(char **tools, char *tooltype, char **names, int def)
	{
	int val;
	char *string = NULL;
		if (tools)
			string = FindToolType(tools, tooltype);
		if (string)
		{
				for (val = 0; names; val++, names++)
					if (MatchToolValue(string, *names))
						return val;
			}
		return def;
	}	

int do_map_arg(char *tooltype, char **names, int def)
	{
		return map_arg(tools, tooltype, names, def);
	}

/* exist_tooltype(tools, tooltype): checks if tooltype exists among tools
 */
BOOL exist_tooltype(char **tools, char *tooltype)
	{
		if (!tools)
			return NULL;
		return (FindToolType(tools, tooltype) != NULL);
	}



/***
 *
 *		Iterators.
 *		Every argument is managed as an iterator
 *		you can resume later. This makes a nice
 *		framework for multiple arguments, pattern-matching,
 *		albums, directory traversal...
 *
 ***/




enum it_tag {CLI_list, Match_list, Dir_list, WB_list, Album, Empty};

struct CLI_it
	{
		int number;
		int index;
		char **names;
	};

struct WB_it
	{
		int number;
		int index;
		struct WBArg *args;
	};
	

#define MAX_LENGTH 400

struct Album_it
	{
		FILE *f;
		char *pbuf;
		char buffer[MAX_LENGTH];
	};

struct Match_it
	{
		struct AnchorPath anchor;
		BYTE suppl_buffer[MAX_LENGTH];
	};
	

/*

struct Dir_List
	{
		struct FileInfoBlock examiner;
	};

 */	
	
/* be careful of the fields you use, the FileInfoBlock/AnchorPath
 * must be longword aligned !
 */
 
struct iterator
	{
		enum it_tag tag;
		struct iterator *next;
		CLEAN auto_clean;
		BPTR save_lock;
		struct DiskObject *currenticon;
		BOOL cd;
		BOOL ownlock;
		union
			{
				struct CLI_it CLI;
				struct WB_it WB;
				struct Album_it Album;
				struct Match_it Match;
			} u;
	};
	


/***
 *
 *		Support functions
 *
 ***/


LOCAL void free_last_icon(struct iterator *it)
	{
		if (it->currenticon)
			FreeDiskObject(it->currenticon);
		it->currenticon = NULL;
	}
	
/* where do we get the tooltypes from anyway
 */	
char **grab_tooltypes(struct iterator *it, char *arg)
	{
	struct DiskObject *icon;
		if (icon_around())
			{
				icon = GetDiskObject(arg);
				if (icon)
					{
						if (it)
							{
								free_last_icon(it);
								it->currenticon = icon;
							}
						else
							ToClean(FreeDiskObject, icon);
						return icon->do_ToolTypes;
					}
			}
		return NULL;
	}
/* we need doFreeAnchorChain 'cause FreeAnchorChain exists only as a pragma
 */  
 
LOCAL void doFreeAnchorChain(struct AnchorPath *ap)
	{
		FreeAnchorChain(ap);
	}


/* getline(buffer, bufsize, f): read a line from file f into buffer,
 * upto bufsize characters. Puts a \0 at the end of line in place of
 * the \n. The last line may end without a \n.
 * If the line is too long, remaining characters are skipped.
 * returns the strlen of buffer, or -1 if the line is too long.
 * A NULL f is allowed, in that case, the call will never succeed.
 * 
 * It is the responsibility of the client to close that file.
 */

LOCAL int getline(char *buffer, int bufsize, FILE *f)
	{
	int i, c;
		if (!f)
			return 0;
		for (i = 0; i < bufsize; i++)
			{
				c = fgetc(f);
				if (c == EOF)
					{
						buffer[i] = '\0';
						return i;
					}
				if (c == '\n')
					{
						buffer[i] = '\0';
						return i;
					}
				buffer[i] = c;
			}
		while((c=fgetc(f)) != '\n' && c != EOF);
		return (-1);
	}

/* The iterators need to change dir in an effective way.
 */
LOCAL void un_dir(struct iterator *it)
	{
		if (it->cd)
			{
				if (it->ownlock)
					UnLock(CurrentDir(it->save_lock));
				else
					CurrentDir(it->save_lock);
			}
		it->cd = FALSE;
		it->ownlock = FALSE;
	}

LOCAL void change_dir(struct iterator *it, BPTR new_dir)
	{
		un_dir(it);
		it->save_lock = CurrentDir(new_dir);
		it->cd = TRUE;
	}

/* iterator creation. An iterator is always the son of another,
 * except the root, of course. new_iterator() initializes all
 * relevant fields to reasonable values.
 */
 	
LOCAL struct iterator *new_iterator(struct iterator *father)
	{
	struct iterator *new;
			/* this is an aligned structure */
		new = AllocMem(sizeof(struct iterator), MEMF_ANY);
		if (!new)
			return NULL;
		new->auto_clean = AllocClean(NIL);
		ToClean2L(new->auto_clean, FreeMem, new, sizeof(struct iterator));
		new->ownlock = FALSE;
		new->cd = FALSE;
		new->next = father;
		new->currenticon = NULL;
		ToCleanL(new->auto_clean, un_dir, new);
		ToCleanL(new->auto_clean, free_last_icon, new);
		return new;
	}

/* insert_iterator(it): returns a new iterator inserted as a son
 * of it, so executed before we resume it. Note that the address of
 * it is necessary.
 */
  
LOCAL struct iterator *insert_iterator(struct iterator **head)
	{
	struct iterator *base;
		base = new_iterator(*head);
		*head = base;
		return base;
	}

/* creation of common iterator types
 */
LOCAL struct iterator *mk_CLI_iterator(struct iterator **head, 
		int argc, char **argv)
	{
	struct iterator *new;
		new = insert_iterator(head);
		new->tag = CLI_list;
		new->u.CLI.names = argv;
		new->u.CLI.number = argc;
		new->u.CLI.index = 0;
		return new;
	}
	
LOCAL struct iterator *mk_WB_iterator(struct iterator **head,
		int argn, struct WBArg *argarray)
	{
	struct iterator *new;
		new = insert_iterator(head);
		new->tag = WB_list;
		new->u.WB.args = argarray;
		new->u.WB.number = argn;
		new->u.WB.index = 0;
		return new;
	}

/* the arp pattern matcher... */
LOCAL char *match_next(struct AnchorPath *ap)
	{
	int res;
		forever
			{
				res = FindNext(ap);
				if (res)
					return NULL;
				if (ap->ap_Info.fib_DirEntryType < 0)
					return ap->ap_Buf;
				if (!ap->ap_Flags & APF_DidDir)
					ap->ap_Flags |= APF_DoDir;
			}
	}

/* ...and the start of it */	
LOCAL char *match_CLI(struct iterator **head)
	{
	struct iterator *first, *new;
		first = *head;
		forever
			{
				if (first->u.CLI.index >= first->u.CLI.number)
					return NULL;
				if (requester_type() != ARP)
					return first->u.CLI.names[first->u.CLI.index++];
				else
					{
					struct AnchorPath *ap;
					int res;
						new = insert_iterator(head);
						new->tag = Match_list;
						ap = &new->u.Match.anchor;
							ap->ap_BreakBits = 0;
							ap->ap_Flags = APF_DoWild;
							ap->ap_StrLen = MAX_LENGTH;
						ToCleanL(new->auto_clean, doFreeAnchorChain, ap);
						res = FindFirst(first->u.CLI.names[first->u.CLI.index++], ap);
						if (!res)
							{
								if (ap->ap_Info.fib_DirEntryType < 0)
									return ap->ap_Buf;
								else
									{
										ap->ap_Flags |= APF_DoDir;
										return match_next(ap);
									}
							}
					}
			}
	}	

/* how to restart an existing iterator */

LOCAL char *resume_iterator(struct iterator **head)
	{
	struct iterator *new, *first;
	struct WBArg *temp;
		first = *head;
		switch(first->tag)
			{
			case CLI_list:
				return match_CLI(head);
			case WB_list:
				if (first->u.WB.index >= first->u.WB.number)
					return NULL;
				temp = first->u.WB.args + first->u.WB.index++;
				if (temp->wa_Lock)
					change_dir(first, temp->wa_Lock);
				return temp->wa_Name;
			case Match_list:
				return match_next(&first->u.Match.anchor);
			case Album:
				if (!getline(first->u.Album.pbuf, MAX_LENGTH, first->u.Album.f))
					{
						/* temporary kludge */
						if (first->u.Album.f)
							{
								fclose(first->u.Album.f);
								first->u.Album.f = NULL;
							}
						return NULL;
					}
				else
					{
						new = mk_CLI_iterator(head, 1, &(first->u.Album.pbuf));
						return match_CLI(head);
					}
			}
	}

LOCAL void delete_first(struct iterator **head)
	{
	struct iterator *next;
		next = (*head)->next;
		CleanUp((*head)->auto_clean);
		*head = next;
	}
	
/* basic stuff about iterators. Still needs a special test
 * about albums.
 */						
LOCAL char *do_next_song(struct iterator **head)
	{
	char *temp;
		forever
			{
				check_abort();
				if (!*head)
					return NULL;
				if (temp = resume_iterator(head))
					return temp;
				else
					delete_first(head);
			}
	}

/* basic code to deal with albums. Once we've detected an album,
 * we can insert it.
 */
LOCAL void insert_album(struct iterator **head, char *arg)
	{
	struct iterator *base;
		base = insert_iterator(head);
		base->tag = Album;
		base->u.Album.pbuf = base->u.Album.buffer;
		base->u.Album.f = fopen(arg, "r");
		/* I can't autoclean this stuff: conflict with Lattice
			ToCleanL(base->auto_clean, fclose, base->u.Album.f);
		 */
	}


char *next_song(struct iterator **head, BOOL req)
	{
	char *test;
	struct iterator *new;
	static struct WBArg holder;
	forever
		{
			test = do_next_song(head);
			if (test)
				{
					if (exist_tooltype(grab_tooltypes(*head, test), "ALBUM"))
						{
							insert_album(head, test);
						}	
					else
						return test;
				}
			else
				{
					if (req && query_name("Module to play", &holder))
						{
							new = mk_WB_iterator(head, 1, &holder);
							new->ownlock = TRUE;
						}
					else
						return NULL;
				}
		}
	}					


/* init_iterator(): builds an initial iterator from the arguments
 * supplied by the user.
 */
LOCAL struct iterator *zero;

LOCAL struct iterator *init_iterator(int argc, char **argv)
	{
	struct iterator *base;
		zero = NULL;
		if (argc)
			{
				msg = NULL;
				base = mk_CLI_iterator(&zero, argc, argv);
			}
		else
			{
				msg = (struct WBStartup *)argv;
				base = mk_WB_iterator(&zero, msg->sm_NumArgs, msg->sm_ArgList);
			}
		return base;
	}

/* insert_args: adds new songs given to the appwindow.
 */
void insert_args(struct iterator **head, struct AppMessage *msg)
	{
	struct iterator *new;
		new = mk_WB_iterator(head, msg->am_NumArgs, msg->am_ArgList);
		ToCleanL(new->auto_clean, ReplyMsg, msg);
	}


/* setup_arguments: initial setup for everything
 */
struct iterator *setup_arguments(int argc, char **argv)
	{
	struct iterator *result;
		result = init_iterator(argc, argv);
		if (argc)
			tools = argv; /* grab_tooltypes(NIL, next_song(&result, FALSE)); */
		else
			tools = grab_tooltypes(NIL, next_song(&result, FALSE));
		return result;
	}
	
