/*
          L - Display files and directories in columns.

          Original effort by Fabio Rossetti.

          (c) 1989 by Fabio Rossetti

          To compile under Lattice C v5.0x use:

		lc -O -v -cus l
		blink lib:cres.o l.o to l lib lib:a.lib lib:lc.lib sd nd

*/

#include <exec/types.h>
#include <exec/ports.h>
#include <exec/io.h>
#include <exec/memory.h>
#include <exec/libraries.h>

#include <devices/console.h>
#include <devices/conunit.h>

#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <libraries/arpbase.h>

#include <intuition/intuitionbase.h>
#include <intuition/intuition.h>

#include <graphics/text.h>

#include <arpfunctions.h>
#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/dos.h>

/* command line arguments */
#define PAT 0
#define CD 1
#define NOCOL 2
#define NARGS 3 /* their # */

/*        */
/* global */
/*        */

struct ArpBase *ArpBase;
struct IntuitionBase *IntuitionBase;
struct Window *CliWin; 	/* pointer to console window */
struct Process *Pr;

/* line arguments stuff, must be accessed by several functions */
LONG argc;
STRPTR argv[NARGS];

/* general purpose string buffers */
TEXT Bf[256]="\0";
TEXT Buf[256]="\0";

/* general purpose lock pointer */
BPTR Lck;

struct FileInfoBlock *Fib;			
TEXT *Path;

/* this is exec stuff for GetWin, to be kept global for Cleanup() */
struct MsgPort iorp = {
    {0, 0, NT_MSGPORT, 0, 0}, 0,
    -1,				/* initialize signal to -1 */
    0,
				/* start with empty list */
    {&iorp.mp_MsgList.lh_Tail, 0, &iorp.mp_MsgList.lh_Head, 0, 0}
};
struct IOStdReq ior = {
    {{0, 0, 0, 0, 0}, &iorp, 0},
    0				/* device is zero */
};
/* pointer to ordered list of files to be displayed */
struct DirectoryEntry *FileList = NULL;

/* Stamptostr() stuff, kept global for future expansions and v5.0 quirks */
struct DateTime D =
	{
	0L,0L,0L,
	FORMAT_DOS,
	NULL,
	NULL,
	NULL,
	NULL };

TEXT 	Date[LEN_DATSTRING],
	Time[LEN_DATSTRING],
	Day[LEN_DATSTRING];	



VOID MemCleanup()
{
}

/* general shutdown routine*/
VOID Cleanup(code,retcode,filelist,anchor)
LONG code;
LONG retcode;
struct DirectoryEntry *filelist;
struct AnchorPath *anchor;

{
	if (ior.io_Device != 0) {
		if (iorp.mp_SigBit != -1) {
		FreeSignal(iorp.mp_SigBit);
		}
		CloseDevice(&ior);
	}

	CloseLibrary((struct Library*)ArpBase);

	if (anchor) FreeAnchorChain(anchor);
	if (filelist) FreeDAList(filelist);
 
	Pr->pr_Result2=retcode; 

	exit(code);
}
/* bulletproofly obtain a pointer to the CLI window sending a ACTION_DISK_INFO
   packet to the console process and looking into InfoData */
struct Window *GetWin()

{
	struct MsgPort *con;
	struct StandardPacket *packet=NULL;
	struct InfoData *id=NULL;

	/* open the console device */
	if ((OpenDevice("console.device", -1, &ior, 0)) != 0) {
	Cleanup(RETURN_FAIL,ERROR_DEVICE_NOT_MOUNTED,NULL,NULL);
	}

	/* set up the message port in the I/O request */
	if ((iorp.mp_SigBit = AllocSignal(-1)) < 0) {
	Cleanup(RETURN_FAIL,ERROR_NO_FREE_STORE,NULL,NULL);
	}
	iorp.mp_SigTask = (struct Task*)Pr;

	/* try to find console associated with calling process */
	/* if started from CLI, than is  */
	if ((iorp.mp_SigTask->tc_Node.ln_Type == NT_PROCESS)) {
	con = (struct MsgPort *) 
		((struct Process *) iorp.mp_SigTask) -> pr_ConsoleTask;
	if (con != 0) {
		if ((packet = (struct StandardPacket *)
			ArpAlloc(sizeof(*packet)))) {
		if ((id = (struct id *) ArpAlloc(sizeof(*id)))) {
			/* this is the console handlers packet port */
			packet->sp_Msg.mn_Node.ln_Name = &(packet->sp_Pkt);
			packet->sp_Pkt.dp_Link = &(packet->sp_Msg);
			packet->sp_Pkt.dp_Port = &iorp;
			packet->sp_Pkt.dp_Type = ACTION_DISK_INFO;
			packet->sp_Pkt.dp_Arg1 = ((ULONG) id) >> 2;
			PutMsg(con, packet);
		 	WaitPort(&iorp);
			/* Pointer to console window, all we need..*/
			return( (struct Window*)(id->id_VolumeNode));
		}
	}
	}
	/* error */
	return((struct Window *)-1);
}
}
/* function to display filenames */
VOID Disp(de,max)
struct DirectoryEntry *de;
LONG max;

{
	TEXT Name[108];
	REGISTER TEXT *from;
	REGISTER WORD i,j,
	/* number of filenames to be displayed in a row, according
	   to the CLI window and its (fixedwidth!) font's sizes */
	nitems =  (((CliWin->Width-24)/CliWin->RPort->Font->tf_XSize)/(max+1));
	
	REGISTER BPTR fh = Output();
	REGISTER BOOL Flag;

	/* if the window is just too small */
	if (nitems < 1) nitems = 1;

	while (de) {

	/* check for ^C */
	if (SetSignal(0,0) & SIGBREAKF_CTRL_C) {
		Puts("***Break");
		Cleanup(RETURN_WARN,NULL,FileList,NULL);
		}

	/* display a row of filenames */
	for(i=1; (i <= nitems) && de ; i++){

	from = de->de_Name;

	/* pad Name string with blanks to columnize names */
	Flag = TRUE;
		for (j = 0; j < max ;j++) {
			if (*(from + j) && Flag) *(Name + j) = *(from + j);
			else {
				Flag = FALSE;
				*(Name + j) = ' ';
			     }
		}
	Name[max] = '\0';
	
	/* display filenames, Write()s are faster than a single Printf() */
	if (de->de_Flags && !argv[NOCOL]) {
		 Write(fh,"\033[33m",5);
		 Write(fh,Name,max);
		 Write(fh," \033[31m",6);			
		}
	else {	Write(fh,Name,max);
		Write(fh," ",1);			
		}
	de = de->de_Next;
	}
	/* carriage return */
	Write(fh,"\n",1);
        }    
}

/* get filenames with Anchors, put them in a ordered DAList, display
   directory name, date, size and # of files */ 
VOID ShowDir()

{
	TEXT EnvBuf[2]; /* buffer to hold the value of 'dateformat'
			   environment variable */
	REGISTER struct DirectoryEntry *De;
	REGISTER struct AnchorPath *Anchor=NULL;
	REGISTER LONG Result,m,max=0;
	REGISTER ULONG Dirs=0,Files=0,Size=0,NumBlocks=0;
	struct DateTime *Dat;

	/* Initialize Stamptostr() stuff */
	Dat = &D;
	Dat->dat_StrDay = Day;
	Dat->dat_StrDate = Date;
	Dat->dat_StrTime = Time;

	/* Allocate space for anchorpath */ 	
	if ( Anchor = (struct AnchorPath *)ArpAlloc( (ULONG)sizeof( *Anchor )) )
	{
		Anchor->ap_Length = 0; /* No path required */
		Anchor->ap_BreakBits = SIGBREAKF_CTRL_C; /* stop if ^C */
	}
	else
	{
		Puts("Error:No memory");
		Cleanup(RETURN_FAIL,ERROR_NO_FREE_STORE,NULL,NULL);
	}

	/* examine files specified by pattern */ 
	Result = FindFirst(argv[PAT],Anchor);

	while ( Result == 0 )
	{
		/* the __builtin_ is the way Lattice inserts inline string functions;
		   with other compilers use a corresponding construct (if any) */
		if((m = __builtin_strlen(Anchor->ap_Info.fib_FileName)) > max)
							 max = m;
		/* add filename to ordered DAList */ 
		if ( !(De=AddDANode(Anchor->ap_Info.fib_FileName,
					 &FileList, 0L, 0L)))
		{
			Puts("Error: no memory");
			Cleanup(RETURN_FAIL,ERROR_NO_FREE_STORE,FileList,Anchor);
		}
		/* update files-size counts & mark entry as dir or file */
		if (Anchor->ap_Info.fib_DirEntryType >= 0) {
				Dirs++;
				De->de_Flags = (BYTE) 1;
				}
		else {
			De->de_Flags = (BYTE) 0;
			Files++;
			}
		Size += Anchor->ap_Info.fib_Size;
		NumBlocks +=Anchor->ap_Info.fib_NumBlocks+1;

		Result = FindNext((struct AnchorPath*) Anchor );
	}

	/* Free the Anchor chain */
  	FreeAnchorChain( Anchor );

	/* error handling */
	if (Result == ERROR_OBJECT_NOT_FOUND) {
		Printf("Error:Can't find %s\n",argv[PAT]);
		Cleanup(RETURN_ERROR,Result,FileList,NULL);
	}
	else if (Result == ERROR_BREAK) {
		Puts("***Break");
		Cleanup(RETURN_WARN,NULL,FileList,NULL);
			}
	else if (Result != ERROR_NO_MORE_ENTRIES) {
		Puts("Error");
		Cleanup(RETURN_ERROR,Result,FileList,NULL);
	}

	argv[PAT][strlen(argv[PAT])-strlen(BaseName(argv[PAT]))] = '\0';	
	/* empty pattern = current directory */
	if (*argv[PAT]) Lck = ArpLock(argv[PAT],ACCESS_READ);
	else Lck = Pr->pr_CurrentDir;
	PathName(Lck,Buf,255);

	/* check for dateformat environment variable and consequentially
	   act over DateTime structure */
	if(Getenv("dateformat",EnvBuf,2))
	switch (EnvBuf[0]) {
		case '1':
		Dat->dat_Format = FORMAT_INT;
		break;
		case '2':
		Dat->dat_Format = FORMAT_USA;
		break;
		case '3':
		Dat->dat_Format = FORMAT_CDN;
		break;
	 };

	/* display directory path with current date/time */
	DateStamp((long *)Dat);
	StamptoStr(Dat);
	Printf("\"%s\" on %s %s %s\n",Buf,Day,Date,Time);

	Disp(FileList,max);

	/* display disk occupation and number of files */
	if (Dirs && !(Files))
		Printf("%ld blocks in %ld dirs.\n",NumBlocks,Dirs);
		else if (Files || Dirs) {
	Printf("%ld bytes, %ld blocks in %ld files",Size,NumBlocks,Files);
	if (Dirs) Printf(" and %ld dirs.\n",Dirs);
	else Printf(".\n");
	}
	else Puts("Directory or Volume is empty");		

	FreeDAList(FileList);
	
		
}


/* _main used instead of main to slim code */
VOID _main(Line)
STRPTR Line;

{
		
	Pr = (struct Process *) FindTask(NULL);
	if(!(ArpBase = (struct ArpBase*)OpenLibrary(ArpName,ArpVersion)))
		 exit(20);
	
	IntuitionBase = (struct IntuitionBase *)ArpBase->IntuiBase;

	if((CliWin = GetWin()) == (struct Window *)-1)
		Cleanup(RETURN_FAIL,ERROR_NO_FREE_STORE,NULL,NULL);

	/* parse command line */
	for (argc=0; argc < NARGS; ++argc)
		argv[argc] = (STRPTR) NULL;

	while(*Line > ' ')
		++Line;

	argc = GADS(++Line,
			strlen(Line),
			"\nUsage: L Pattern [CD] [NOCOL]\n",
			argv,
			"PAT,CD/S,NOCOL/S");


	/* analyze user given pattern and make it suitable for FindFirst()
	   and FindNext() */

	/* parent dir */
 	if (!Strcmp(argv[PAT],"/")) {
				strcat(Bf,"/*");/* Why in the world do I have to */
				argv[PAT]=Bf;	/* do this mess to keep code     */
					}	/* LIB:cres.o compatible ??????  */
	/* root dir */
	else if (!Strcmp(argv[PAT],":")) {
				strcat(Bf,":*");
				argv[PAT]=Bf;
					}
	/* current dir */
	else if (*argv[PAT] == '\0')  {
				strcat(Bf,"*");
				argv[PAT]=Bf;
				}
	/* some dir */
	else {
		if (Lck=Lock(argv[PAT],ACCESS_READ)) /* something on disk ? */ 
			{
			if(!(Fib=ArpAllocMem(sizeof(struct FileInfoBlock),
					MEMF_CLEAR))) 
					Cleanup(RETURN_FAIL,
					ERROR_NO_FREE_STORE,NULL,NULL);
			
			(VOID)Examine(Lck,Fib);
			/* really a directory ? */
			if (Fib->fib_DirEntryType >= 0) {
				strcpy(Bf,argv[PAT]);
				if (argv[PAT][strlen(argv[PAT])-1] == ':') 
				strcat(Bf,"*");
				else strcat(Bf,"/*");
				argv[PAT]=Bf;

				}
			UnLock(Lck);
			}
		}	
				
	ShowDir();

	/* if required change the current dir with listed directory in a 
	   PROMPT "%S" compatible way */
	if (argv[CD]) {
		/* get directory name */
		argv[PAT][strlen(argv[PAT])-strlen(BaseName(argv[PAT]))] = '\0';
		if (argv[PAT][strlen(argv[PAT])-1] == '/') 
				argv[PAT][strlen(argv[PAT])-1] = '\0';
		/* null pattern = current dir */
		if (*argv[PAT]) {
			/* change the current dir lock */
			Lck = Lock(argv[PAT],ACCESS_READ);
			UnLock(Pr->pr_CurrentDir);
			Pr->pr_CurrentDir = Lck;
			}
		else Lck = Pr->pr_CurrentDir; /* or keep it ..*/
		PathName(Lck,Buf,255);
		CtoBStr(Buf,(BSTR)((((struct CommandLineInterface *)
			((Pr->pr_CLI) << 2))->cli_SetName)),255);	
		}
	Cleanup(RETURN_OK,NULL,NULL,NULL);
}

