/*

  DiskFree - returns number of free disk blocks.

*/
#include <vo_stdio.h>
#include <volink.h>     /* Linker directives like DLLENTRY */
#include <apploader.h>  /* RunLibraryFunction etc */
#include <timers.h>
#include <libaudiodec.h>
#include <vsostasks.h>
#include <consolestate.h>
#include <ctype.h>
#include <uimessages.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <audio.h>
#include <aucommon.h>
#include <limits.h>
#include <sysmemory.h>
#include <kernel.h>
#include <vo_fat.h>
#include <crc32.h>


int verbose = 0;

u_int32 VoFatCountFreeKiB(register VO_FILE *f, register u_int32 maxMiB, register u_int16 verbose);

void PrintKiB(register const char *preString, register u_int32 nKiB, register const char *postString, register s_int16 humanReadable) {
  printf("%s", preString);
  if (humanReadable) {
    if (nKiB < 1000) {
      printf("%3dK", (s_int16)nKiB);
    } else {
      double f = nKiB * (1.0/1024.0);
      char unit = 'M';
      if (nKiB >= 1000*1024L) {
	unit = 'G';
	f *= (1.0/1024.0);
      }
      if (f < 10.0) {
	printf("%3.1f%c", floor(f*10.0)*0.1, unit);
      } else {
	printf("%3ld%c", (s_int32)floor(f), unit);
      }
    }
  } else {
    printf("%10ld", nKiB);
  }
  printf("%s", postString);
}

ioresult PrintClusters(register const char *dirName, register u_int32 *wPointer, register u_int16 humanReadable) {
  FILE *fp;
  u_int32 resKiB;
  ioresult res = S_ERROR;
  static int titleLine = 0;
  static int firstHumanReadable = -1;
  int hasReset = 0;

 retry:
  resKiB = 0xFFFFFFFFU;

  if (strlen(dirName) < 2 || dirName[1] != ':') {
    if (!wPointer) {
      printf("E: Illegal file name %s\n", dirName);
    }
    goto finally;
  }

  if (firstHumanReadable < 0) firstHumanReadable = humanReadable;


  fp = fopen(dirName, "s");
  if (fp) {
#if 0
    if (fp->dev->fs != &FatFileSystem) {
      return SysError("Not a FAT disk");
    }
#endif
    if (!__F_CHARACTER_DEVICE(fp)) {
      resKiB = VoFatCountFreeKiB(fp, 0xFFFFFFFFU, verbose > 1 && !wPointer);
      if (resKiB == 0xFFFFFFFFU && !hasReset) {
	hasReset = 1;
	ioctl(fp->dev, IOCTL_RESTART, NULL);
	fclose(fp);
	goto retry;
      }

      if (!wPointer && ((!titleLine && verbose) || verbose > 1)) {
	titleLine = 1;
	if (firstHumanReadable) {
	  printf("Drv Used  Free Total  Use%%  Name\n");
	} else {
	  printf("Drv  Used/KiB   Free/KiB  Total/KiB  Use%%  Name\n");
	}
      }

      if (!wPointer) {
	printf("%c: ", toupper(dirName[0]));
	if (!verbose) {

	  PrintKiB("", resKiB, firstHumanReadable ? "" : " KiB", firstHumanReadable);
	  printf(" free, %s\n",
		 fp->dev->Identify(fp->dev, NULL, 0));
	} else {
	  FatDeviceInfo *di=(FatDeviceInfo*)fp->dev->deviceInfo;
	  // di->totalClusters includes non-existent clusters 0 and 1
	  u_int32 totalClusters = di->totalClusters-2;
	  u_int16 sectorsPerCluster = di->fatSectorsPerCluster;
	  u_int32 totalKiB = totalClusters * di->fatSectorsPerCluster / 2;
	  const char *preStr = firstHumanReadable ? " " : "";
	  PrintKiB(preStr, totalKiB-resKiB, " ", firstHumanReadable);
	  PrintKiB(preStr, resKiB         , " ", firstHumanReadable);
	  PrintKiB(preStr, totalKiB       , " ", firstHumanReadable);
	  printf(" %3d%%  %s\n",
		 (int)(100.5-100.0*resKiB/totalKiB),
		 fp->dev->Identify(fp->dev, NULL, 0));
	}
      }
      fclose(fp);
    }
  } else {
    if (!wPointer) {
      printf("E: Illegal device %c:\n", toupper(dirName[0]));
      goto finally;
    }
  }
  res = S_OK;

 finally:
  if (wPointer) {
    *wPointer = resKiB;
  }
  return res;
}

ioresult main(char *parameters) {
  int nParam, i;
  FILE *fp = NULL;
  char *p = parameters;
  int nextIsWPointer = 0, noOp = 1;
  int humanReadable = 0;
  ioresult retCode = S_OK;
  u_int32 *wPointer = NULL;

  nParam = RunProgram("ParamSpl", parameters);
  for (i=0; i<nParam; i++) {
    if (nextIsWPointer) {
      wPointer = (void *)(strtol(p, NULL, 0));
      nextIsWPointer = 0;
    } else if (!strcmp(p, "-h")) {
      printf("Usage: DiskFree [-p x|-H|+H|-v|-h] [D:]\n"
	     "D:\tPrint results for drive D (:: means all drives)\n"
	     "\tThere may be multiple drive parameters.\n"
	     "\tDrive parameters must be after -p|-H options.\n"
	     "-p x\tStore last result in KiB to X-memory 32-bit pointer"
	     " pointed to by x\n"
	     "\t(Usable for calling with RunLibraryFunction(); "
	     "makes operation silent)\n"
	     "-H|+H\tDisplay sizes in human-readable form on/off\n"
	     "-v\tVerbose (giving twice makes even more verbose)\n"
	     "-h\tShow this help\n");
      goto finally;
    } else if (!strcmp(p, "-p")) {
      nextIsWPointer = 1;
    } else if (!strcmp(p, "-H")) {
      humanReadable = 1;
    } else if (!strcmp(p, "+H")) {
      humanReadable = 0;
    } else if (!strcmp(p, "-v")) {
      verbose++;
    } else if (p[0] && p[1] == ':') {
      if (p[0] == ':') {
	int ii;
	for (ii='A'; ii<='Z'; ii++) {
	  if (vo_pdevices[ii-'A']) {
	    p[0] = ii;
	    retCode += PrintClusters(p, wPointer, humanReadable);
	  }
	}
      } else {
	retCode += PrintClusters(p, wPointer, humanReadable);
      }
      noOp = 0;
    } else {
      printf("E: Unknown parameter \"%s\"\n", p);
      retCode = S_ERROR;
      goto finally;
    }
    p += strlen(p)+1;
  }

  if (noOp) {
    retCode += PrintClusters(currentDirectory, wPointer, humanReadable);
  }
  
 finally:
  return retCode;
}
