/********************************************************
**                                                     **
**     $VER: Expand.c started on 03.09.2001            **
**                                                     **
**     Handle filename completition and wildwards      **
**                                                     **
**     Free software written by T.Pierron              **
**                                                     **
********************************************************/

#include <libraries/gadtools.h>
#include <graphics/gfxbase.h>
#include <exec/memory.h>
#include <exec/lists.h>
#include <dos/dos.h>
#include <dos/exall.h>
#include <dos/datetime.h>
#include <dos/dosextens.h>

#include "Execute.h"
#define  CATCOMP_NUMBERS
#include "ExecuteStrs.h"

#define	EXALL_BUF		1024
#define	MAX_FILENAME	40
#define	LV_MAX_FILE		16

extern UBYTE RemPartial;

/*** Test if a string represent an AmigaDOS pattern ***/
STRPTR IsPat( STRPTR name, UBYTE force )
{
	int    len = strlen( name ) * 2 + 6;
	STRPTR tok;
	/* Buffer for exall scan */
	if(tok = (STRPTR) AllocVec(len + EXALL_BUF, MEMF_CLEAR))
	{
		if( ParsePatternNoCase(name, tok+EXALL_BUF, len) > 0 ) return tok;
		if( force == TRUE )
		{
			/* The string doesn't contain any meta-character, add #? at the end */
			strcpy(tok, name); strcat(tok, "#?");
			if( ParsePatternNoCase(tok, tok+EXALL_BUF, len) > 0 ) return tok;
		}
		/* Everything failed, fucking day... */
		FreeVec( tok );
	}
	return NULL;
}

/*** Expand wildcards ***/
void ExpandWild( struct StringInfo *sti, BYTE type )
{
	STRPTR pat = GetPath(sti->Buffer, sti->BufferPos);
	if(pat != NULL)
	{
		STRPTR pattern = IsPat( (STRPTR) FilePart(pat), FALSE );
		if(pattern != NULL)
		{
			struct ExAllControl *eac;
			struct ExAllData    *ead;
			struct FileLock     *lock;

			((STRPTR)PathPart(pat))[0] = 0;
			if(lock = (APTR) Lock(pat, SHARED_LOCK))
			{
				APTR oldcwd = (APTR) CurrentDir( lock );
				if (eac = (APTR) AllocDosObject(DOS_EXALLCONTROL,NULL))
				{
					eac->eac_LastKey = 0;
					eac->eac_MatchString = pattern+EXALL_BUF;
					RemPartial = TRUE;
					for(;;) {
						ExAll(lock, pattern, EXALL_BUF, ED_TYPE, eac);
						if( eac->eac_Entries == 0 ) break;
						for(ead = (APTR) pattern; ead; ead = ead->ed_Next)
							/* If object type is the right one and filename can be added */
							if((type == EXPAND_PAT ||
							   (ead->ed_Type < 0) == (type == EXPAND_FILE)) &&
							   (AddPath(sti, pat, ead->ed_Name) == 0))
								goto stop_now;
					}
					if(RemPartial == TRUE) /* No match! Avert user */
						DisplayBeep( NULL );
					stop_now: FreeDosObject(DOS_EXALLCONTROL, eac);
				}
				UnLock( CurrentDir( oldcwd ) );
			}
			FreeVec( pattern );
		}
		FreeVec( pat );
	}
}

/*** Compute max item shown in the list ***/
WORD max_height( struct Node * node, UWORD YSize, WORD *nbit )
{
	WORD Height = YSize + 10, nb;
	for(nb=0; node->ln_Succ && nb<LV_MAX_FILE; Height+=YSize, node=node->ln_Succ, nb++);
	while( node->ln_Succ ) nb++, node = node->ln_Succ;
	*nbit = nb-1;
	return Height;
}

/*** Show matches in a window ***/
STRPTR ShowMatches( struct List * list )
{
	extern struct GfxBase *GfxBase;
	extern struct Gadget  *strgad;
	extern struct Window  *window;
	extern struct Screen  *screen;
	struct Window *   win;
	struct TextAttr   attr;
	struct TextFont * def;
	WORD   selected, nbitems;

	def = GfxBase->DefaultFont;
	attr.ta_Name  = def->tf_Message.mn_Node.ln_Name;
	attr.ta_YSize = def->tf_YSize;
	attr.ta_Style = FS_NORMAL;
	attr.ta_Flags = def->tf_Flags;
	selected      = def->tf_XSize * strlen(list->lh_Head->ln_Name) + 50;

	if( win = (APTR) OpenWindowTags( NULL,
			WA_InnerWidth,  selected,
			WA_InnerHeight, max_height(list->lh_Head, def->tf_YSize, &nbitems) + 10,
			WA_Left,        window->LeftEdge + (window->Width - selected >> 2),
			WA_Top,         strgad->TopEdge  + window->TopEdge + strgad->Height + 3,
			WA_Title,       ErrMsg(FNC_WINDOW_TITLE),
			WA_IDCMP,       IDCMP_CLOSEWINDOW | LISTVIEWIDCMP | IDCMP_RAWKEY,
			WA_Flags,       RMBTRAP | WFLG_ACTIVATE | WFLG_DRAGBAR | WFLG_CLOSEGADGET,
			WA_PubScreen,   screen,
			TAG_DONE))
	{
		struct NewGadget  ng;
		struct Gadget   * glist = NULL, * gad;
		extern void     * vi;
		UBYTE             quit = FALSE;

		ng.ng_TextAttr   = &attr;
		ng.ng_VisualInfo = vi;

		/* String gadget for user command */
		ng.ng_GadgetText = NULL;
		ng.ng_Width      = win->Width - 10 - (
		ng.ng_LeftEdge   = 10);
		ng.ng_Height     = win->Height - win->BorderBottom - (
		ng.ng_TopEdge    = win->BorderTop+4);

		gad = (APTR) CreateGadget(LISTVIEW_KIND, CreateContext(&glist), &ng,
					GTLV_Labels,   list, GTLV_ShowSelected, NULL, 
					GTLV_Selected, 0,    TAG_DONE);

		/* Refresh everything */
		AddGList     (win,  glist, (UWORD)-1, (UWORD)-1, NULL);
		RefreshGList (glist,  win, NULL,      (UWORD)-1);

		GT_RefreshWindow(win, NULL);

		/* Handle events */
		for(selected = 0; quit == FALSE; )
		{
			extern struct IntuiMessage *msg, msgbuf;

			WaitPort( win->UserPort );

			while( msg = (APTR) GT_GetIMsg( win->UserPort ) )
			{
				CopyMemQuick(msg, &msgbuf, sizeof(*msg));
				GT_ReplyIMsg(msg);

				switch( msgbuf.Class )
				{
					case IDCMP_CLOSEWINDOW: quit = TRUE; selected = -1; break;
					case IDCMP_GADGETUP:    quit = TRUE; selected = msgbuf.Code; break;
					case IDCMP_RAWKEY:
						switch( msgbuf.Code )
						{
							case TAB_KEY:
								if(msgbuf.Qualifier & SHIFTKEYS) goto case_UP;
							case CURSORDOWN:
								selected++; if(selected > nbitems) selected = 0;
								break;
							case CURSORUP:
							case_UP:
								selected--; if(selected < 0) selected = nbitems;
								break;
							case 69: selected = -1;
							case 67:
							case 68: quit = TRUE;
							default: continue;
						}
						GT_SetGadgetAttrs(gad, win, NULL, GTLV_Selected, selected, GTLV_MakeVisible, selected, TAG_DONE);
				}
			}
		}
		CloseWindow( win );
		FreeGadgets( glist );

		/* Get selected entry */
		if( selected >= 0 )
		{
			struct Node * node;
			STRPTR        p;
			for( node = list->lh_Head ; selected-- ; node = node->ln_Succ );
			for( p = (STRPTR)(node+1) + MAX_FILENAME - 1; *p == ' ';  p-- );
			p[1] = 0;
			return node->ln_Name;
		}
	}
	return NULL;
}

static STRPTR a3arg;
static UWORD  max;

#ifdef	__GNUC__
static void PutChProc( void )
{
	register UBYTE data __asm("d0");

#else									/* Same proc with SAS/C */
static void __asm PutChProc(register __d0 UBYTE data, register __a3 STRPTR out)
{
#endif
	/* Simple but efficient output buffer string */
	if( max-- > 0 ) *a3arg++ = data;
	else *a3arg = 0;
}

/*** Convert (struct ExAllData *) to string ***/
void ObjToStr( struct ExAllData *ead )
{
	ULONG ArgRDF[3];
	UBYTE Fmt[64];

	/* File size or type */
	if( ead->ed_Type > 0 ) strcpy(Fmt, "%10s"),  ArgRDF[0] = (ULONG) ErrMsg(DIR_ENTRY);
	else                   strcpy(Fmt, "%10ld"), ArgRDF[0] = ead->ed_Size;

	/* Protection flags */
	{	STRPTR flags; UWORD i;
		ead->ed_Prot ^= 0xf; strcat(Fmt, " sparwed");
		flags = Fmt + strlen(Fmt) - 1;
		for(i = FIBB_DELETE; i <= FIBB_SCRIPT; i++, flags--)
			if( (ead->ed_Prot & (1 << i)) == 0 ) *flags = '-';
	}
	/* Date of modification */
	{	struct DateTime dt;
		strcat(Fmt, " %s %s");
		CopyMem(&ead->ed_Days, &dt.dat_Stamp, sizeof(dt.dat_Stamp));
		dt.dat_Format = dt.dat_Flags = 0;
		dt.dat_StrDay = NULL;
		dt.dat_StrDate = -LEN_DATSTRING + (
		dt.dat_StrTime = Fmt + (sizeof(Fmt) - LEN_DATSTRING));

		DateToStr( &dt );
		ArgRDF[1] = (ULONG) dt.dat_StrDate;
		ArgRDF[2] = (ULONG) dt.dat_StrTime;
	}

	/* Format the string */
	RawDoFmt(Fmt, ArgRDF, PutChProc, NULL);
}

/*** Add a node into the list view ***/
WORD AllocNode( struct List *list, struct ExAllData *ead, char regfile )
{
	struct Node *new, *node;
	WORD   len = strlen(ead->ed_Name);

	/* Discard .info files if desired */
	if( (InfoFlags & CHECKED) == 0 && !Stricmp(ead->ed_Name + len - 5, ".info") )
		return 1;

	if( new = (APTR) AllocVec(sizeof(*new) + 128, 0) )
	{
		/* Now it can be inserted into the list view */
		new->ln_Pri  = 0;
		new->ln_Name = (STRPTR) (new+1) + 127 - len;
		strcpy(new->ln_Name, ead->ed_Name);
		max   = (STRPTR) new->ln_Name - (
		a3arg = (STRPTR) (new+1) + MAX_FILENAME);

		if( regfile )
		{
			new->ln_Type = (ead->ed_Type > 0 ? 0 : 1);
			/* Add a slash after directory name */
			if(new->ln_Type == 0) strcat(new->ln_Name, "/"), len++;
			/* Get object properties */
			ObjToStr( ead );
		}
		else /* This is a device/assign/volume */
		{
			strcpy(a3arg, ErrMsg(MSG_VOLUME - 2 + (
			new->ln_Type = ead->ed_Type)));
		}

		/* Insert into list of matching files */
		for( node = list->lh_Head ; node->ln_Succ ; node = node->ln_Succ )
		{
			if( node->ln_Type != new->ln_Type ) {
				/* Ordered according to increasing ln_Type */
				if( new->ln_Type < node->ln_Type ) break;
			} else if( Stricmp(node->ln_Name, new->ln_Name) > 0 )
				break;
		}
		/* Get node where to insert new one */
		Insert(list, new, node->ln_Pred);

		return len;
	}
	return 0;
}

/*** Set ln_Name node's fields to something useful for user ***/
void SetName( struct List * list, WORD max )
{
	struct Node * node = list->lh_Head;
	STRPTR        src, dest;
	for( ; node->ln_Succ ; node = node->ln_Succ )
	{
		register WORD nb;
		dest = (STRPTR) (node + 1) + MAX_FILENAME - max;
		src  = node->ln_Name; node->ln_Name = dest;
		for(nb = max; nb && (*dest++ = *src++); nb--);
		if(nb > 0) dest--;
		for(; nb--; *dest++ = ' ');
	}
}

/*** Complete beginning of a file name ***/
void CompleteFilename( struct StringInfo *sti, BYTE type )
{
	STRPTR pat = GetPath(sti->Buffer, sti->BufferPos);
	struct List list_file;

	if(pat != NULL)
	{
		struct ExAllControl *eac;
		struct ExAllData    *ead;
		struct FileLock     *lock;
		STRPTR               pattern, startpath;
		UWORD                maxlen = 0;

		NewList( &list_file );

		/* Check if file is already a pattern or force to be one */
		if( pattern = IsPat( (STRPTR) FilePart(pat), TRUE) )
		{
			(startpath = (STRPTR) PathPart(pat))[0] = 0;
			/* Locate directory part of filename to complete */
			if(lock = (void *) Lock(pat, SHARED_LOCK))
			{
				APTR oldcwd = (APTR) CurrentDir( lock );
				WORD len, more = 1;
				/* Now scan for possible entries */
				if (eac = (APTR) AllocDosObject(DOS_EXALLCONTROL, NULL))
				{
					eac->eac_LastKey = 0;
					eac->eac_MatchString = pattern+EXALL_BUF;
					/* Collect possible match for path */
					while( more ) {
						more = ExAll(lock, pattern, EXALL_BUF, ED_DATE, eac);
						if( eac->eac_Entries == 0 ) break;
						for(ead = (APTR) pattern; ead; ead = ead->ed_Next)
							/* Check for undesired objects */
							if( type == COMPLETE_OBJ || (ead->ed_Type > 0) ==
							  ( type == COMPLETE_DIR ) )

							/* Alloc a node and get max character len of node's name */
							if(0 == (len = AllocNode(&list_file, ead, TRUE)))
								goto stop_now;
							else if( maxlen < len ) maxlen = len;
					}
					stop_now: FreeDosObject(DOS_EXALLCONTROL, eac);
				}
				else DisplayBeep( NULL );
				UnLock( CurrentDir( oldcwd ) );
			}
			else AvertUser( ErrMsg(DIR_NOT_FOUND) );

			/* We have enterred a path and this is the it's first component **
			** As there was no results in the current directory, check for  **
			** a device/assign/volume matching the given pattern            */
			if( startpath == pat && list_file.lh_Head->ln_Succ == NULL )
			{
				struct DosList *DosList = (APTR) LockDosList(LDF_READ | LDF_ALL);
				struct ExAllData ead;

				while ( DosList = (struct DosList *) NextDosEntry(DosList, LDF_ALL) )
				{
					STRPTR Name = (STRPTR) BADDR(DosList->dol_Name);
					WORD   Len  = *Name;

					if(Len >= EXALL_BUF-1) Len = EXALL_BUF-2;
					CopyMem(Name+1, pattern, Len); CopyMem(":", pattern+Len, 2);

					if( MatchPatternNoCase(pattern+EXALL_BUF, pattern) )
					{
						switch( DosList->dol_Type )
						{
							case DLT_VOLUME:    ead.ed_Type = 2; break;
							case DLT_DIRECTORY: ead.ed_Type = 3; break;
							case DLT_DEVICE:    ead.ed_Type = 4;
						}
						ead.ed_Name = pattern;
						if(0 == (Len = AllocNode(&list_file, &ead, FALSE)))
							break;
						else if( maxlen < Len ) maxlen = Len;
					}
				}
				UnLockDosList(LDF_READ|LDF_ALL);
			}
			FreeVec( pattern );
		}
		else DisplayBeep( NULL );

		/* Check if we have found some entries */
		if( list_file.lh_Head->ln_Succ != NULL )
		{
			/* We have found one or more entries. If just one, replace it with **
			** current path, otherwise, show possibilities in a new window     */
			struct Node * node  = list_file.lh_Head, *next;
			STRPTR        entry ;
			if(node->ln_Succ->ln_Succ == NULL)
				entry = node->ln_Name;
			else
				SetName( &list_file , maxlen+1 ),
				entry = ShowMatches( &list_file );

			/* User cancel the selection? */
			if( entry != NULL )
			{
				RemPartial = TRUE;
				AddPath(sti, pat, entry);
			}
			/* Free results */
			for( node = list_file.lh_Head ; next = node->ln_Succ ; node = next )
				FreeVec( node );
		}
		FreeVec(pat);
	}
}

