{**************************************************************
 ***                                                        ***
 ***                     FILE UTILITIES                     ***
 ***                                                        ***
 **************************************************************}
{$I CompFlgs}
{methods used to maintain files, etc, but used occasionally, so no
desire to make part of file unit which would use up memory.  Also, not
part of kmaint, which has all sorts of includes of specialist units, so
anything that includes it starts including other units.}

unit IndxUtil;

INTERFACE

uses indexes;

procedure QuickSort(IndexStream : PIndexStream; const DoJimmys : boolean);
function MakeHoles(IndexStream : PIndexStream; Spacing : byte) : word; {returns cancel if cancelled}

IMPLEMENTATION

uses  jimmys, jimindxs, {for indexed files}
{$IFDEF WINDOWS}
	wui,	{windows}
{$ELSE}
	tui, {text}
{$ENDIF}
			minilib, {for messages}
			app, {for desktop}
			dosUtils,{}
			help,
			global,
			files, {dbase message}
			tuimsgs; {progress box}

{***************************************************************************
 ***                                                                     ***
 ***                    QUICK-SORT INDEX                                 ***
 ***                                                                     ***
 ***************************************************************************}
{Useful for those occasions when the index is a tad corrupted,
 or for analysis, when the records can be added on to the
 end and then quicksorted rather than inserted all the way
 through (rather slow)}

procedure QuickSort(IndexStream : PIndexStream; const DoJimmys : boolean);
var
	SortFactor : string;
	IndexItem, IndexItem2 : PIndexItem;
	x : string;
	ProBox : PMessageBox;
	ProBoxSize : byte;
	Scale : real;
	Jimmy : PJimmy;

	procedure SwapItems(i,j : longint);
	begin
		{don't bother to swap if i=j}
		if i<>j then begin
			 IndexItem  := PIndexItem(IndexStream^.GetAt(i)); {IndexItem^.RecordSize := IndexStream.RecSize; {}
			 IndexItem2 := PIndexItem(IndexStream^.GetAt(j)); {IndexItem2^.RecordSize := IndexStream.RecSize;{}
			 IndexStream^.PutAt(j, IndexItem);
			 IndexStream^.PutAt(i, IndexItem2);

			 {reset main file pointers}
			 if DoJimmys then begin
					if IndexItem^.Idx2Dat<>-1 then
						SetJimmyIdxPtr(IndexItem^.ixType, IndexItem^.Idx2Dat, j);

					if IndexItem2^.Idx2Dat<>-1 then
						SetJimmyIdxPtr(IndexItem2^.ixType, IndexItem2^.Idx2Dat, i);
			 end;

			 dispose(Indexitem, done);
			 dispose(IndexItem2, done);
		end;
	end;

	function KeyAt(RecNo : longint) : string;
	begin
		IndexItem := PIndexItem(IndexStream^.GetAt(RecNo));
		if IndexItem=nil then
			KeyAt := ''
		else begin
			KeyAt := IndexItem^.GetKey;
			dispose(IndexItem, done);
		end;
	end;

	function ScaledPos(Rec : longint) : byte;
	begin ScaledPos := trunc(Scale*Rec)+1; end;

	procedure Sort(l, r: Integer);
	var	i, j : longint;
			iKey, jKey : string;
			DispLine1,DispLine2 : string;
			LeftPos, RightPos : byte; {just for display}

		procedure PosChange(Lo, Hi : longint);
		begin
			if Lo<>Hi then begin
				while Lo<=Hi do begin DispLine1[Lo] := 'Ä'; inc(Lo); end; {draw line}
				ProBox^.SetMessage(SortFactor+#13#10+DispLine1);
			end;
		end;

		begin
			{==== QUICK SORT ALGORITHM=======}
			i := l; j := r; x := KeyAt((l+r) DIV 2);

			{Display wots goin' on}
			SortFactor := SortFactor +'*';
			DispLine1 := space(ProBoxSize);  {use x to display how sort is doing}
			LeftPos := ScaledPos(i);  {Scaled pos for updating display during search}
			RightPos := ScaledPos(j);
			if LeftPos=RightPos then DispLine1[LeftPos] := #197  {Å}
									 else begin DispLine1[LeftPos] := 'Ã'; DispLine1[RightPos] := '´'; end;    {Ã ´}
			ProBox^.SetMessage(SortFactor+#13#10+DispLine1);
			if ProBox^.COmmand = cmCancel then exit;

			{=========Actual sorty bit==========}
			repeat
														{This is the meaty bit       And this is just to display}
				while KeyAt(i) < x do begin i := i + 1; if (i mod 100) =0 then PosChange(LeftPos+1,ScaledPos(i)); end;
				while x < KeyAt(j) do begin j := j - 1; if (j mod 100) =0 then PosChange(ScaledPos(j),RightPos-1); end;

				if i <= j then begin {if the wrong way around}

          if i<>j then begin {don't actually swap if same place!}

						{Put in 'S' - sorting markers - into progress box - use separate string to keep temporary}
						DispLine2:=DispLine1; DispLine2[ScaledPos(i)] := 'S'; DispLine2[ScaledPos(j)] := 'S';
						ProBox^.SetMessage(SortFactor+#13#10+DispLine2);

						{Do meaty bit - swap}
						SwapItems(i,j);
					end;

					i := i + 1; j := j - 1; {This is done even if they are the same}
				 end;

			until i > j;

			if l < j then Sort(l, j);
			if i < r then Sort(i, r);

			SortFactor := Copy(SortFactor,2,length(SortFactor)); {chop off one *}
		end;

begin
	if IndexStream^.NoRecs<=1 then exit; {no point...}

	{Display bits}
	SortFactor := '';
	ProBox := NewMessageBox('SORTING '+IndexStream^.FileName,space(40),mfInformation+mfCancelButton,hcIndexSortBox);
	ProBoxSize := ProBox^.Size.X -2;
	Scale := ProBoxSize/IndexStream^.NoRecs;

	{Sort bit}
	Sort(0,IndexStream^.NoRecs-1);

	{Tidy up bit}
	if ProBox^.Command=cmCancel then PauseMessage('Sorting','Abandoned',hcNoContext) {else PauseMessage('Done','')};
	Dispose(ProBox, done);
end;



{***************************************************************************
 ***                                                                     ***
 ***               CREATE HOLES IN INDEX                                 ***
 ***                                                                     ***
 ***************************************************************************}
{This has been modified a bit to try and speed things up due to fixit doing
it automatically, and this being a real pain if it's just a limited part of
the file that's getting clogged}
function MakeHoles;
var
	 OldIndexStream : PIndexStream;
	 IndexItem : PIndexItem;
	 OldRec,NewRec,RealPos : longint; {Index record}
	 OldNoRecs : longint;
	 ProBox : PProgressBox;
	 NotClogged : boolean;
	 UpText : string;
   Jimmy : PJimmy;

begin
	IndexItem := nil;
	if IndexStream = nil then exit;

	ProBox := NewProgressBox('HOLE INSERTER','     '+IndexStream^.FileName+'     ',0, hcIndexHoleInsert);

	{========= FIRST CHECK TO SEE IF HOLES NEEDED =============}
	{This is the bit that helps speed up fixit/etc - it basically runs through
	checking every 5th indexitem is a hole and all others aren't - ignoring the
	first one (0) as the creator ignores it}
	OldRec := 0; NotClogged := True;
	OldNoRecs := IndexStream^.NoRecs-1; {so no need to keep dereferencing/etc}

{	while NotClogged and (OldRec<=OldNoRecs) do begin

		if (OldRec mod 10) = 0 then ProBox^.Update('Checking '+IndexStream^.FileName,OldRec,OldNoRecs);

		IndexItem := PIndexItem(INdexStream^.GetAt(OldREc));

		if IndexItem = nil then
			NotClogged := False {start main}
{		else begin
			if (OldRec>0) and ((OldRec mod spacing) = 0) then begin
				if not IndexItem^.Hole then NotClogged := False;
			end else
				if IndexItem^.Hole then NotClogged := False; {well it's not spaced right anyway}

{			dispose(IndexItem, done);
		end;
		inc(OldRec);
	end;
	ThinkingOff;
  {}

	if OldRec<OldNoRecs then begin

		ProBox^.Update('Possible clog found at '+N2Str(OldRec),0,0);

		FileAdmin(fiJimmys)^.LogOn;

		{======== CREATE WORK-FROM FILE ==========================}
		ThinkingOn('Copying Data...');
		{The new index MUST be created from scratch in case the new index is
		smaller than the previous - eg large amount of deletions, or a convertion
		where the index has a gap of 2 rather than 5...}
		{actually, the new index must just be truncated.  See below}
		DeleteFile(DataPath+'HOLEIDX.$$$');

		{Create old index stream to work from}
		New(OldIndexStream, init('HOLEIDX.$$$', IndexStream^.RecSize));
		OldIndexStream^.Seek(0);
		IndexStream^.Seek(0); {set both to start}
		OldIndexStream^.CopyFrom(IndexStream^, IndexStream^.GetSize); {copy complete stream}
		{no need to do this	- truncate afterwards IndexStream^.Seek(0); IndexStream^.Truncate;  {Delete "new" index}
		ThinkingOff;

		{--- Start main hole maker ---}
		{OldRec is now at the point *after* where the first hole should go}
		if OldRec<=OldNoRecs then dec(OldRec,2); {now go back to one before so insert below works OK}
		if OldRec<0 then OldRec := 0;
		NewRec := OldRec;
		UpText := 'Inserting Holes in '+GetFileName(IndexStream^.FileName);
		while OldRec<=OldNoRecs do begin

			if (OldRec mod 10) = 0 then ProBox^.Update(UpText+#13#10+'New:'+N2Str(NewRec)+'  Old:',OldRec,OldNoRecs);

			{Get index record from old position}
			IndexItem := PIndexItem(OldIndexStream^.GetAt(OldRec));

			{Validate}
			if IndexItem = nil then begin
				DBaseMessage(OldIndexStream, 'Cannot retrieve Index Item, Rec '+N2Str(OldRec)+#13#10
																			+'Marking as hole',mfError,hcInternalErrorMsg);
				New(IndexItem, init);
				IndexItem^.Hole := True;  {Mark as hole}
			end;

			if not IndexItem^.Hole then begin

        {---- VALIDATE JIMMY PTR --------}
        {$IFDEF fixit}
      	  Jimmy := PJimmy(JimmyStream^.GetAt(IndexItem^.Idx2Dat));

          if (Jimmy = nil) then begin
						DBaseMessage(OldIndexStream, 'Cannot retrieve Jimmy from Index Item, Rec '+N2Str(OldRec)+#13#10
																			+'Marking as hole',mfError,hcInternalErrorMsg);
             IndexItem^.Hole := true;
             IndexItem^.Idx2Dat := -1;
          end else begin
          	dispose(Jimmy, done);
          end;
        {$ENDIF}


				{------- STORE IN NEW FILE ------}
				IndexStream^.PutAt(NewRec, IndexItem);

				{Update data file's pointer}
				if IndexItem^.Idx2Dat<>-1 then
						SetJimmyIdxPtr(IndexItem^.ixType, IndexItem^.Idx2Dat, NewRec);

				inc(NewRec);
			end;

			{--- Add a new hole? =----}
			{no point in inserting at beginning - hole search goes up recs of file}
			if (NewRec<>0) and ((NewRec mod Spacing) = 0) then begin
				{Create new index item from last one (so ordering is kept}
				IndexItem^.Hole := True;
				IndexItem^.Idx2Dat := -1; {Set pointer, safety measure}
				{put into file}
				IndexStream^.PutAt(NewRec,IndexItem);{Store at end of file}
				inc(NewRec);
			end;

			dispose(IndexItem, done);
			inc(OldRec);
		end; {while}

		dispose(OldIndexStream, done);

		IndexStream^.Truncate; {after last index}

		FileAdmin(fiJimmys)^.LogOff;
	end;

	MakeHoles := ProBox^.Command;

	Dispose(ProBOx, done);{}
end; {proc}

end.

