-- RopeFromFileImpl.Mesa
-- written by Bill Paxton,  February 1981
-- last edit by Bill Paxton, 17-Jan-82 15:52:46

DIRECTORY
	RopeFrom,
	RopeEdit,
	RopeEditingAlloc,
	RopeReader,
	Rope,
	RopeInline,
	RTStorageOps,
	File,
	Space,
	Directory,
	Inline,
	IOStream,
	RopeEditingBLT;

RopeFromFileImpl: MONITOR
   IMPORTS
	RopeEditingBLT, RopeEditingAlloc, RopeReader, Rope, RopeInline, RopeEdit,
	RopeFrom, Space, Inline, Directory, RTStorageOps, IOStream
   EXPORTS RopeFrom
   SHARES Rope =
BEGIN
OPEN fileI:File, RopeFrom, RopeInline;

pagesPerSpace: NAT = RopeReader.pagesPerArray;
charsPerPage: NAT = RopeReader.charsPerPage;
charsPerArray: NAT = RopeReader.charsPerArray;
charsPerSpace: NAT = charsPerArray;
spaceMin: NAT = charsPerSpace/2; -- put in shared array if less than 1/2
swapUnitSize: NAT = 16; -- based on Paul's advice

CharsArray: TYPE = RopeReader.CharsArray;
Chars: TYPE = REF CharsArray;

-- ***** Operations

File: PUBLIC PROC [
	file: fileI.Capability,
	start: Offset ← 0,
	length: Offset ← MaxLen, -- defaults to rest of file
	activate: BOOLEAN ← FALSE,
	fileLen: Offset ← MaxLen,
	okToMapFile: BOOLEAN ← TRUE]
	RETURNS [rope: ROPE] = {

	MakeRope: PROC [startSpace, numToDo: Offset]
			RETURNS [rope: ROPE] = {
		chars: REF RopeReader.CharsArray;
		fileSpace: Space.Handle;
		len: NAT ← charsPerSpace;
		firstSpace: BOOLEAN ← FALSE;
		IF numToDo = 0 THEN RETURN [NIL];
		IF numToDo > 1 THEN { -- split into balanced parts
			half: Offset ← numToDo/2;
			front: ROPE ← MakeRope[startSpace, half];
			back: ROPE ← MakeRope[startSpace+half, numToDo-half];
			RETURN [Rope.Concat[front,back]] };
		IF startSpace+1 = numSpaces THEN -- this is the last space
			len ← len - endSkip;
		IF startSpace = 0 THEN { -- this is the first space for the file
			len ← len - startSkip; firstSpace ← TRUE };
		IF ~okToMapFile OR len < spaceMin THEN { -- don't make a separate space for it
			start, nwords: NAT;
			charOffset, skipWords: NAT ← 0;
			base: ROPE;	
			IF firstSpace THEN {
				-- make skipWords*2+charOffset=startSkip
				charOffset ← startSkip MOD 2;
				skipWords ← startSkip/2 };
			nwords ← (len+charOffset+1)/2; -- number to copy from file
			[chars, start, base] ← RopeEditingAlloc.AllocWords[nwords];
			fileSpace ← GetSpace[];
			Space.Map[fileSpace, 
				Space.WindowOrigin[file,
					startPage+startSpace*pagesPerSpace]];
			Space.Activate[fileSpace];
			Inline.LongCOPY[from: Space.LongPointer[fileSpace]+skipWords,
				nwords: nwords, to: LOOPHOLE[chars,LONG POINTER]+start/2];
			FreeSpace[fileSpace];
			RETURN [qZone.NEW[Tsubstr ← [node[substr[len,base,start+charOffset,RopeEdit.Depth[base]+1]]]]] };
		[chars,fileSpace] ← GetArraySpace[];
		Space.Map[fileSpace,Space.WindowOrigin[file,
				startPage+startSpace*pagesPerSpace]];
		IF swapUnitSize > pagesPerSpace THEN
			Space.CreateUniformSwapUnits[swapUnitSize, fileSpace];
		IF activate THEN Space.Activate[fileSpace];
		rope ← RopeReader.CharsRope[chars,fileSpace];
		IF len < charsPerSpace THEN
			rope ← qZone.NEW[Tsubstr ← [node[substr[len,
				rope, IF firstSpace THEN startSkip ELSE 0,
				1+ RopeEdit.Depth[rope]]]]]};

	GetFileLen: PROC = {
		name: STRING ← [128]; -- don't really want this, but GetProps needs it
		[,,,fileLen,] ← Directory.GetProps[file,name] };

	startPage: Offset;
	startSkip, endSkip: NAT;
	numPages, numSpaces: Offset;
	
	start ← start+charsPerPage; -- to skip over leader page
	startPage ← start/charsPerPage;
	startSkip ← LOOPHOLE[Inline.LowHalf[start MOD charsPerPage]];
	IF fileLen = MaxLen THEN GetFileLen;
	fileLen ← fileLen+charsPerPage; -- to compensate for leader page
	IF fileLen <= start THEN RETURN [NIL];
	length ← MIN[length,fileLen-start];
	IF length = 0 THEN RETURN[NIL];
	numPages ← (start+length+charsPerPage-1)/charsPerPage-startPage;
	numSpaces ← (numPages+pagesPerSpace-1)/pagesPerSpace;
	endSkip ← LOOPHOLE[Inline.LowHalf[numSpaces*charsPerSpace-length-startSkip]];
	rope ← MakeRope[0,numSpaces]};

Stream: PUBLIC PROC [stream: IOStream.Handle, length: Offset ← MaxLen]
	RETURNS [rope: ROPE] = {

	MakeRope: PROC [len: Offset] RETURNS [ROPE] = {
		chars: REF RopeReader.CharsArray;
		start, nchars, charloc: NAT;
		base: ROPE;
		IF len = 0 THEN RETURN [NIL];
		IF len > charsPerSpace THEN { -- split into balanced parts
			half: Offset ← MAX[len/2,charsPerSpace];
			front: ROPE ← MakeRope[half];
			back: ROPE ← MakeRope[len-half];
			RETURN [Rope.Concat[front,back]] };
		nchars ← Short[len];
		[chars, start, base] ← RopeEditingAlloc.AllocWords[(nchars+1)/2];
		charloc ← start; 
		UNTIL nchars = 0 DO
			nread: NAT;
			block.length ← 0;
			nread ← IOStream.GetBlock[stream,block,nchars];
			RopeEditingBLT.StringToArrayBlt[block,0,chars,charloc,nread];
			nchars ← nchars-nread; charloc ← charloc+nread;
			ENDLOOP;
		RETURN [qZone.NEW[Tsubstr ← [node[substr[len,base,start,1+RopeEdit.Depth[base]]]]]] };

	start,fileLen: Offset;
	block: REF TEXT;
	
	fileLen ← IOStream.GetLength[stream];
	start ← IOStream.GetIndex[stream];
	IF fileLen <= start THEN RETURN [NIL];
	length ← MIN[length,fileLen-start];
	IF length = 0 THEN RETURN [NIL];
	block ← RopeEditingAlloc.GetBlock[];
	rope ← MakeRope[length];
	RopeEditingAlloc.FreeBlock[block] };

-- ***** These two routines are the only monitored procedures
-- ***** They provide a shared space for reading files

fileSpace: Space.Handle ← Space.nullHandle; -- shared file space

GetSpace: ENTRY PROC RETURNS [space: Space.Handle] = {
	space ← IF fileSpace # Space.nullHandle THEN fileSpace ELSE
					Space.Create[pagesPerSpace, Space.virtualMemory];
	fileSpace ← Space.nullHandle };

FreeSpace: ENTRY PROC [space: Space.Handle] = {
	IF fileSpace = Space.nullHandle THEN Space.Unmap[fileSpace ← space]
	ELSE Space.Delete[space] };


-- Operation to get a space for file mapping

GetArraySpace: PROC RETURNS [chars: REF CharsArray, space: Space.Handle] = {
	chars ← NARROW[RTStorageOps.NewCollectibleSubspace[pagesPerSpace,CODE[CharsArray]]];
	space ← Space.GetHandle[Space.PageFromLongPointer[LOOPHOLE[chars]]] };
	

-- ***** Initialization

StartRopeFromFile: PUBLIC PROC = {
	};

END.