(* Copyright 2021-2024 Sebastian Lederer. See the file LICENSE.md for details *)
program reclaim;
var volname:string;
    ch:char;
    count:integer;

(* we use some stuff internal to stdlib.pas *)
procedure getdirslot(volumeid:integer;slotNo:integer;var result:DirectorySlot;var error:integer);
	external;
procedure putdirslot(volumeid:integer;slotNo:integer;var dirslot:DirectorySlot;var error:integer);
	external;

procedure scanVolume(volname:string;dryrun:boolean;verbose:boolean;var reclaimCount:integer);
var volid:integer;
    i:integer;
    error:integer;
    dirslot:DirectorySlot;
    done:boolean;
    fileCount, deletedCount:integer;
    freeCount:integer;
    fileSlotCount:integer;
    reservedCount:integer;
    freeAreaCount:integer;
    inFreeArea:boolean;
    endSlot:integer;
    lastUsed:integer;
    deletedExtent:boolean;

procedure clearDirSlot;
begin
	reclaimCount := reclaimCount + 1;

	if not dryrun then
	begin
		dirslot.name := '';
		dirslot.flags := [SlotFree];
		dirslot.sizeBytes := 0;
		dirslot.createTime := 0;
		dirslot.modTime := 0;
		dirslot.generation := 0;

		putdirslot(volid, i, dirslot, error);
		if error <> IONoError then
		begin
			write('Error writing directory slot ',i);
			writeln(': ', ErrorStr(error));
			done := true;
		end;
	end;
end;

procedure markLastSlot;
var slotNo:integer;
begin
	(* we actually mark the slot after the last used slot *)
	if not dryrun then
	begin
		if lastUsed < endSlot then
		begin
			writeln('Updating directory...');
			slotNo := lastUsed + 1;
			getdirslot(volid, slotNo, dirslot, error);
			if error <> IONoError then
			begin
				write('Error reading directory slot ', slotNo);
				writeln(': ', ErrorStr(error));
			end;

			if not (SlotEndScan in dirslot.flags) then
				dirslot.flags := dirslot.flags + [SlotEndScan];

			putdirslot(volid, slotNo, dirslot, error);
			if error <> IONoError then
			begin
				write('Error writing directory slot ', lastUsed);
				writeln(': ', ErrorStr(error));
			end;
		end;
	end;
end;

procedure beginFreeArea;
begin
	freeCount := freeCount + 1;
	if not inFreeArea then
	begin
		inFreeArea := true;
		freeAreaCount := freeAreaCount + 1;
	end;
end;

procedure endFreeArea;
begin
	if inFreeArea then
		inFreeArea := false;
end;

begin
	volid := findvolume(volname);
	if volid < 1 then
		writeln('Volume ', volname, ' not found.')
	else
	begin
		done := false;
		deletedExtent := false;
		inFreeArea := false;
		fileCount := 0;
		deletedCount := 0;
		reclaimCount := 0;
		freeCount := 0;
		reservedCount := 0;
		fileSlotCount := 0;
		freeAreaCount := 0;
		lastUsed := 0;

		openvolumeid(volid);
		i := volumeTable[volid].startSlot;
		endSlot := volumeTable[volid].part.dirSize - 1;

		if verbose then
		begin
			write('Volume ', volname);
			write('  start slot:', i);
			write('  dir size: ', endSlot + 1);
			writeln('  extent size: ', volumeTable[volid].part.extentSize);
		end;

		writeln('Reading directory...');
		repeat	
			getdirslot(volid, i, dirslot, error);
			if error <> IONoError then
			begin
				write('Error reading directory slot ',i);
				writeln(': ', ErrorStr(error));
				done := true;
			end
			else
			begin
				if SlotEndScan in dirslot.flags then
					done := true;
				if SlotFirst in dirslot.flags then
				begin
					lastUsed := i;
					fileCount := fileCount + 1;
					deletedExtent := false;
					endFreeArea;
				end
				else
				if SlotDeleted in dirslot.flags then
				begin
					deletedCount := deletedCount + 1;
					deletedExtent := true;
					clearDirSlot;
					(* we consider a deleted file
						as a free area here *)
					if not dryrun then
						beginFreeArea;
				end
				else
				if SlotExtent in dirslot.flags then
				begin
					if deletedExtent then
						clearDirSlot
					else
						lastUsed := i;
				end
				else
				if SlotReserved in dirslot.flags then
					reservedCount := reservedCount + 1
				else
				if SlotFree in dirslot.flags then
					beginFreeArea;
			end;
			if i = endSlot then
				done := true;
			i := i + 1;
		until done;

		markLastSlot;
		closevolumeid(volid);
		i := i - 1;

		if verbose then
		begin
			writeln('last used slot: ', lastUsed);
			writeln('max slots:      ', endSlot + 1);
			writeln('free slots:     ', endSlot - i + freeCount);
			writeln('reserved slots: ', reservedCount);
			writeln;
		end;

		write(fileCount, ' files, ', deletedCount, ' deleted files, ');
		write(reclaimCount);
		if dryrun then
			writeln(' reclaimable slots, ', freeAreaCount, ' free regions.')
		else
			writeln(' reclaimed slots, ', freeAreaCount, ' free regions.');
	end;
end;

begin
	if ParamCount > 0 then
		volname := ParamStr(1)
	else
	begin
		write('Volume name> ');
		readln(volname);
	end;
		
	initDevices;
	scanVolume(volname, true, true, count);

	if count > 0 then
	begin
		write('Proceed with reclaim (y/n)? ');
		read(ch);
		writeln;
		if upcase(ch) = 'Y' then
			scanVolume(volname, false, false, count);
	end;
end.
