/*
 * du.c
 * (c) Musus Umbra, 1996
 */

/* Standard ANSI includes */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

/* We only use the _kernel_swi function in kernel.h (this makes the code
 * portable with no changes between EasyC and Acorn's C
 */
#include "kernel.h"

/* And since we only need two swis, it's quicker to #define them than to suck
 * in the whole of swis.h :-)
 */
#define OS_File 0x08
#define OS_GBPB 0x0c

/* width of field to print size info in: (chars) */
#define SIZEWIDTH "12"


char *commarise(char*);		/* 2362513Kbytes -> 2,362,513Kbytes conversion */
char *k(int/*,int*/);		/* convert bytes to Kbytes/Mbytes/etc */
int du_dir(char*,int,int);	/* du a directory */
void do_help(char**);		/* print an array of strings on stderr */
int dir_size(char*);		/* obtain the size of a directory */


/*
 * The help message (also the bad switch message)
 */
char *help[] = {
"Syntax: du [flags] [dir] [flags] [dir] ...\n",
"du displays the total size of a directory (and any subdirectories).\n",
"flags are:\n",
"  -b     : turn on 'byte' size mode [off]\n",
"  -k     : turn on 'K' size mode [on]\n",
"  -n     : turn on 'auto' size mode [off]\n",
"  -c, -C : turn on, off commas in numbers [on]\n",
"  -r, -R : turn on, off displayed size rounding [on]\n",
"  -d, -D : include, exclude the size of directories [include]\n",
"  -i, -I : treat images as directories, files [files]\n",
"  -a<n>  : file sizes are rounded up to the nearest <n> bytes [1024]\n",
"  -A     : reset rounding to 1\n",
"  -m<n>  : max depth for verbosity ( 0<=n<=256 )  [256]\n",
"  -M     : reset max verbosity depth to 256\n",
"  -s     : print total size only (equivalent to -m0)\n",
"  -h     : give this help\n",
"With the exception of the on/off flags, flags are case insensitive.",
"Flags are read left to right.  Directories are counted as encountered,",
"with whatever flags are in effect at that time.",
0 };


/* Global flags/option vars for efficiency :-) */
int bytesmode = 2;  	/* 0=>auto, 1=>bytes, 2=>K */
int dirflag = 1;		/* whether to include the size of directories */
int maxdepth = 256;		/* maximum depth to be verbose at */
int lookinimages = 0;	/* are images directories? */
int roundto = 1024;		/* round all sizes (except 0) up to */
int disproundflag = 1;	/* do we round displayed sizes? */
int commaflag = 1;		/* do we put commas in big numbers? */

int main(int argc, char *argv[])
{
	int i;					/* index into command tail (argv[i])*/
	int gd = 0;				/* how many directories we're asked for */
	int dd = 0;				/* how many we actually count */
	int tmp;				/* name says it all really :=) */
	int donehelp = 0;		/* have we printed the help message? */
	int totalsize = 0;		/* the running total of the directories */
	int swindex;			/* index into argument strings */

	for (i=1; i<argc; i++)
	{
		if ( argv[i][0] == '-' )	/* if a switch */
		{
			swindex = 0;
			while (++swindex)
			{
				switch (argv[i][swindex])	/* */
				{
					case 0   : swindex = -1; break;
					case 'B' : /* b flag case insensitive */
					case 'b' : bytesmode = 1; break;      /* byte mode */
					case 'K' : /* k flag case insensitive */
					case 'k' : bytesmode = 2; break;      /* K mode */
					case 'N' : /* n flag case insensitive */
					case 'n' : bytesmode = 0; break;      /* auto mode */
					case 'c' : commaflag = 1; break;
					case 'C' : commaflag = 0; break;
					case 'r' : disproundflag = 1; break;  /* round d.sizes */
					case 'R' : disproundflag = 0; break;  /*  */
					case 'd' : dirflag = 1; break;        /* add dir on */
					case 'D' : dirflag = 0; break;        /* off */
					case 'i' : lookinimages = 1; break;   /* images are dirs*/
					case 'I' : lookinimages = 0; break;   /* images are files */
					case 'a' : roundto = atoi(argv[i]+swindex+1); /* round to */
							   while (isdigit(argv[i][swindex+1]))
							   {
							       swindex++;
							   }
							   break;
					case 'A' : roundto = 1; break;			/* reset rounding */
					case 's' :
					case 'S' : maxdepth = 0; break;
					case 'm' : maxdepth = atoi(argv[i]+swindex+1);  /* max verb. dpth */
							   while (isdigit(argv[i][swindex+1]))
							   {
							       swindex++;
							   }
							   break;
					case 'M' : maxdepth = 256; break;     /* reset verb. dpth */
					case 'h' : /* help case insensitive! */
					case 'H' : do_help(help); donehelp = 1; break;
					default  : do_help(help); exit(1);    /* bad option :-( */
				}
			}
		}
		else						/* ie. not a switch */
		{
			gd++;					/* got one more dir to du */
			tmp = du_dir(argv[i],0,dir_size(argv[i]));
			if (tmp>=0)				/* if du was successful */
			{
				dd++;				/* one more dir du'd */
				totalsize += tmp;	/* add size to running total */
			}
		}
	}

	if ( !gd && !donehelp )		/* if no dirs in command args then */
	{
		tmp = du_dir("@",0,dir_size("@"));	/* du CSD */
		if (tmp>=0)							/* if that is successful */
		{
			dd++;							/* done one more dir */
			totalsize += tmp;				/* add to running total */
		}
	}

	switch (dd)
	{
		case 0 : if ( !donehelp )
				 {
				    printf("Nothing to count!\n");		/* no dirs done */
				 }
				 break;
		case 1 : break;								/* just one dir done */
		default: printf("%"SIZEWIDTH"s - Total\n",k(totalsize/*,bytesmode*/));
	}
}


/*
 * print array of strings pointed to by m.  Array should be terminated with a
 * NULL pointer.  Output is to error stream.
 */
void do_help(char **m)
{
	while (*m)
	{
		fprintf(stderr,*m);
		m++;
	}
}


/*
 * convert the (integer) size in bytes into a string, based on the value
 * of (global) 'bytesmode':
 *     0 - in bytes if <4K, in Kbytes if <4M, in Mbytes otherwise
 *     1 - in bytes
 *     2 - in Kbytes
 * the value will be rounded (not truncated) if (global) 'disproundflag' is
 * non-0 (bytes are never rounded :-)
 * If the (global) flag 'commaflag' is non-zero then the resulting string
 * will have ','s inserted every three digits from the end. (eg. x,yyy,zzz)
 */
char *k(int bytes /*,int bytesmode*/ )
{
	const int one_kilo = 1024;					/* one K in bytes */
	const int one_meg = one_kilo * one_kilo;	/* one M in bytes */
	const int four_kilos = 4 * one_kilo;		/* 4K in bytes */
	const int four_megs = 4 * one_meg;			/* 4M in bytes */
	const int kilo_round = one_kilo/2;			/* value for rounding K */
	const int meg_round = one_meg/2;			/* value for rounding M */
	static char ret[64];
	char  unit = '!';
	int   divisor = 1;
	int   roundmung;

	switch (bytesmode)
	{
		case 0 : /* auto range */
			unit = ' ';
			divisor = 1;				/* assumbe bytes unless... */
			roundmung = 0;
			if (bytes>=four_kilos)		/* >4K so use Kbytes */
			{
				unit = 'K';
				divisor = one_kilo;
				roundmung = kilo_round;
			}
			if (bytes>=four_megs)		/* >4M so use Mbytes */
			{
				unit = 'M';
				divisor = one_meg;
				roundmung = meg_round;
			}
			break;
		case 1 : /* bytes mode */
			unit = ' ';
			divisor = 1;
			roundmung = 0;
			break;
		case 2 : /* K mode */
			unit = 'K';
			divisor = one_kilo;
			roundmung = kilo_round;
			break;
		default : /* bad mode */
			fprintf(stderr,"bad mode flag to k function\n");
			exit(1);
	}

	if ( roundmung && disproundflag )			/* ie. !bytes & rounding */
	{
		bytes += roundmung;
	}
	sprintf(ret,"%d%c",bytes/divisor,unit);
	if (commaflag)
	{
		commarise(ret);
	}
	return ret;
}



/*
 * round up an (integer) number of bytes to the nearest (global) 'roundto'
 * number of bytes
 */
int round(int bytes)
{
	if (bytes % roundto)
	{
		bytes += (roundto - ( bytes % roundto ) );
	}
	return bytes;
}


/*
 * du the directory 'dir', being verbose to (global) depth 'maxdepth'.
 * If (global) 'dirflag' is non-0 then 'mysize' is added to the size of the
 * directory returned.
 * 'depth' is the depth of this directory into the du.
 * NB: RECURSIVE
 */
int du_dir(char *dir, int depth, int mysize)
{
   static char block[64];			/* block for GBPB, static for efficiency */
   _kernel_swi_regs r;
   _kernel_oserror *e;
   char pathname[256];
   int  offset;
   int  read;
   int *objsize = (int*) (block+8);		/* block!8 is object's size */
   int *objtype = (int*) (block+16);	/* block!16 is object's type */
   char *objname = (block+20);			/* block$20 os object's name */
   int du_size = 0;						/* initial size of dir is 0 */
   int tmp;

   if (mysize<0)						/* if not a dir/(image && -i) */
   {
      printf("%s - no such directory. Skipped.\n",dir);
      return 0;
   }

   if (dirflag) { du_size += mysize; }	/* if including dir sizes, add it */

   offset = 0;							/* start at obj. 0 (ie. start) of dir */
   do
   {
      r.r[0]=10;              /* OS_GBPB 10 */
      r.r[1]=(int) dir;       /* ->directory name */
      r.r[2]=(int) block;     /* ->buffer */
      r.r[3]=1;               /* entries to read */
      r.r[4]=offset;          /* offset to start from */
      r.r[5]=64;              /* size of buffer */
      r.r[6]=(int) "*";       /* filename to match */
      e=_kernel_swi(OS_GBPB,&r,&r);
      if (e)
      {
          fprintf(stderr,"*** FS error: %s ***\n",e->errmess);
          return -1;
      }
      read = r.r[3];			/* how many entries read (1 or 0) */
      offset = r.r[4];			/* offset for next time round loop */

      if (read>0)				/* if read an entry */
      {
         switch ( *objtype )
         {
            case 1 : /* a file */
                     du_size += round(*objsize);	/* add size to count */
                     break;
         	case 3 : /* an image */
         	         if ( !lookinimages )	/* if treating images as files */
         			 {
         			 	du_size += round(*objsize);
         			 	break;
         			 }						/* if treating as dirs ... */
            case 2 : /* a directory */
					 strcpy(pathname,dir);
					 if ((dir[strlen(dir)-1])!=':' && (dir[strlen(dir)-1])!='.')
					 {
					 	strcat(pathname,".");
					 }
					 strcat(pathname,objname);
            		 tmp = du_dir(pathname, depth+1, *objsize);
            		 if (tmp>=0) { du_size += tmp; }	/* in case du failed */
                     break;
            default: ; /* Huh? what the hell is *this* doing here?!?! */
         }
      }
   } while (offset!=-1);	/* loop until no more to read */

   if (depth<=maxdepth)		/* if we're at < (global) 'maxdepth' */
   {
      printf("%"SIZEWIDTH"s %s\n",k(du_size/*,bytesmode*/),dir);
   }
   return du_size;
}


/*
 * find the size of directory (or image) 'dir'.  If (global) 'lookinimages'
 * is 0 then images are treated as files, otherwise they are treated as
 * directories.
 * Returns the size of the directory (ie. how much disc space it eats)
 * or -1 if not found or a file.
 */
int dir_size(char *dir)
{
	_kernel_swi_regs r;
	_kernel_oserror *e;
	int size;

	r.r[0] = 17;						/* read catalogue info */
	r.r[1] = (int) dir;					/* -> object name */
	e = _kernel_swi( OS_File, &r, &r);
	if ( e )							/* trap errors */
	{									/* 'not found' *is not* an error */
		printf("%s\n",e->errmess);
		return -1;
	}
	switch ( r.r[0] )
	{
		case 3 : if ( !lookinimages ) { size =-1; break; }	/* Image */
		case 2 : size = r.r[4]; break;						/* Directory */
		case 1 :	/* File */
		case 0 :	/* Not found */
		default: size = -1;
	}

	return size;
}


char *commarise(char *in)
{
	static char scratch[64];
	int j = 63;
	int d = 0;
	int i;

	scratch[j--] = 0;
	for ( i=strlen(in); !isdigit(in[i]) && i>=0; i-- )
	{
		scratch[j--] = in[i];
	}
	for ( ; i>=0; i-- )
	{
		scratch[j--] = in[i];
		d++;
		if (d==3 && i>0)
		{
			scratch[j--] = ',';
			d = 0;
		}
	}
	strcpy(in,scratch+j+1);
	return in;
}
