//	B-Tree Maintenance Routines
// Copyright Xerox Corporation 1979, 1980, 1981, 1982


//	BTreeWrtMS0.bcpl  -- Routines for writing in B-Trees
//		Uses pagination strategy MS to minimize the sum of lengths
//		of records posted to the father page.

//	last edited May 9, 1982  5:24 PM by Taft

get "btree.decl"


external
	[ Allocate	// Defined by OS
	Free
	MoveBlock
	DefaultArgs
	Dvec

	ReadBTreePage	// defined by OpenBTree
	WriteBTreePage
	LockBTreePtr
	UnlockBTreePtr
	BTreeBug

	PathRecLE		// Defined in BTreeRead.bcpl
	BackUpOneRecord
	PopStack
	ReadStack
	ReadStackAndLockBTreePtr
	GetRecord
	]


external
	[ UpdateRecord	// Defined in BTreeWrtMS.bcpl
	InsertRecords
	ComplexInsertRecords
	HairyInsertRecords
	MakeNewRoot
	PageLength
	EntryLength
	FillLeftPage
	FillRightPage
	AppendEntSeqEnt
	PushEntSeqEnt
	MakeEntSeqEnt
	ComputeEntLens
	PushEntSeqEntLens
	AppendEntSeqEntLens
	DepositESL
	FindRightBrother
	FindLeftBrother
	RemoveEntry
	WriteRightBrother
	WritePage
	AddToHeap
	TrickleDown
	RemoveFromHeap
	SiftUp
	FabricateIS
	FreeIS
	]


let UpdateRecord(Tree, Key, RecordGenerator, Param, CKR, PathStk, UseExistingPath;
	numargs na) be

	[ DefaultArgs(lv na, -3, 0, Tree>>TREE.CompareKeyRtn, size PS/16, false)
	if PathStk eq size PS/16 then
		[ Dvec(UpdateRecord, lv PathStk); UseExistingPath = false ]

	let LR = Tree>>TREE.LengthRtn
	let Zone = Tree>>TREE.Zone

	let OrigST = PathRecLE(Tree, PathStk, UseExistingPath, Key, CKR)

	let FoundRecord = Empty
	let FoundRecLen = nil
	if PathStk>>PS.PathStkTop ge 1 then
		[ FoundRecord = GetRecord(Tree, PathStk, false)
		FoundRecLen = LR(FoundRecord)
		unless CKR(Key, FoundRecord) eq 0 do
			[ Free(Zone, FoundRecord); FoundRecord = Empty ]
		]

	let Record = RecordGenerator(FoundRecord, Param)
	let RecLen = LR(Record)

	unless CKR(Key, Record) eq 0 do
		BTreeBug(Tree, ecRecGenReturnedWrongKey)

	// This update may change the tree in ways that invalidate any
	// existing PathStk.
	Tree>>TREE.Version = Tree>>TREE.Version+1

	// To minimize average insertion time, perform the update in one of
	// three ways (in increasing order of difficulty, as measured by amount
	// of temporary storage allocated and amount of data copied):
	// (1) If replacing an existing record of the same size, just overwrite it.
	// (2) If the new record fits on the page (after removing the old record
	//     if any), just slide up the records beyond the insertion point and
	//     insert the new record.
	// (3) Otherwise, leave the new record as an ESE at the appropriate
	//     stack level, and let InsertRecords cope with the problem.

	let PSE = lv (PathStk>>PS.PSE↑(PathStk>>PS.PathStkTop))
	if FoundRecord ne Empty & RecLen eq FoundRecLen then
		[ // New record same length as old, just copy it over
		let PagePtr = WriteBTreePage(Tree, PSE>>PSE.PageNo)
		LockBTreePtr(Tree, lv PagePtr)
		MoveBlock(lv (PagePtr+PSE>>PSE.LastOffset)>>BTE.Record,
			Record, RecLen)
		UnlockBTreePtr(Tree, lv PagePtr)
		Free(Zone, Record)
		return
		]

	let OldRecGrPtr = Empty
	test FoundRecord eq Empty
	ifso	[ PathStk>>PS.PathStkTop = OrigST
		PSE = lv (PathStk>>PS.PSE↑(PathStk>>PS.PathStkTop))
		Tree>>TREE.RecordCount = Tree>>TREE.RecordCount+1
		Tree>>TREE.StateDirty = true
		]

	ifnot	[ BackUpOneRecord(Tree, PSE)
		Free(Zone, RemoveEntry(Tree, PSE, lv OldRecGrPtr))
		]


	let EntLen = RecLen+(offset BTE.Record/16)

	if EntLen le ReadStack(Tree, PathStk)>>BTP.FreeWords then
		[ // New record fits on the page -- insert it the easy way
		let PagePtr = WriteBTreePage(Tree, PSE>>PSE.PageNo)
		LockBTreePtr(Tree, lv PagePtr)
		let NewEnt = PagePtr + PSE>>PSE.Offset
		let FreeEnt = PagePtr + Tree>>TREE.PageLength -
			PagePtr>>BTP.FreeWords
		while FreeEnt gr NewEnt do
			[ // slide up in chunks -- MoveBlock goes the wrong way
			MoveBlock(FreeEnt, FreeEnt-EntLen, EntLen)
			FreeEnt = FreeEnt - EntLen
			]
		MoveBlock(lv NewEnt>>BTE.Record, Record, RecLen)
		NewEnt>>BTE.GrPtr = OldRecGrPtr
		PagePtr>>BTP.FreeWords = PagePtr>>BTP.FreeWords - EntLen
		UnlockBTreePtr(Tree, lv PagePtr)
		Free(Zone, Record)
		return
		]

	let RecPtr = Allocate(Zone, EntLen)
	MoveBlock(lv (RecPtr>>BTE.Record), Record, RecLen)
	RecPtr>>BTE.GrPtr = OldRecGrPtr
	Free(Zone, Record)
	AppendEntSeqEnt(PSE, MakeEntSeqEnt(Zone, RecPtr, EntLen))

	Free(Zone, RecPtr)

	let IS = FabricateIS(Tree, PathStk)

	while (PathStk>>PS.PathStkTop ge 0) &
		(PathStk>>PS.PSE↑(PathStk>>PS.PathStkTop).ESLFront
			ne Empty) do
		[ InsertRecords(Tree, PathStk, IS)
		PopStack(PathStk)
		]

	FreeIS(IS)
	]


and MakeEntSeqEnt(Zone, EntSeq, Length) = valof

	[ let E = Allocate(Zone, (offset ESLE.EntSeq/16)+Length)
	E>>ESLE.EntSeqP = lv E>>ESLE.EntSeq
	E>>ESLE.EntSeqLen = Length
	MoveBlock(lv E>>ESLE.EntSeq, EntSeq, Length)
	resultis E
	]


and AppendEntSeqEnt(PathStkEntry, Entry) be

	[ Entry>>ESLE.FwdP = Empty

	test PathStkEntry>>PSE.ESLRear eq Empty
	ifso	PathStkEntry>>PSE.ESLFront = Entry
	ifnot	(PathStkEntry>>PSE.ESLRear)>>ESLE.FwdP = Entry

	PathStkEntry>>PSE.ESLRear = Entry
	]


and FabricateIS(Tree, PathStk) = valof

	[ let Zone = Tree>>TREE.Zone
	let IS = Allocate(Zone, size IS/16)
	IS>>IS.Tree = Tree

	let MaxFreeWords = Tree>>TREE.PageLength-PageOverhead
	IS>>IS.MaxFreeWords = MaxFreeWords
	IS>>IS.AwfullyFull = (9*MaxFreeWords)/10
	IS>>IS.PrettyFull = (2*MaxFreeWords)/3
	IS>>IS.FairlyFull = MaxFreeWords/2
	IS>>IS.BreathingSpace = MaxFreeWords-IS>>IS.AwfullyFull

	IS>>IS.PathStk = PathStk
	IS>>IS.HeapPtr = Allocate(Zone, size HP/16)
	IS>>IS.ELLPtr = Allocate(Zone, size ELL/16)
	resultis IS
	]


and FreeIS(IS) be

	[ let Zone = (IS>>IS.Tree)>>TREE.Zone
	Free(Zone, IS>>IS.HeapPtr)
	Free(Zone, IS>>IS.ELLPtr)
	Free(Zone, IS)
	]

and InsertRecords(Tree, PathStk, IS) be

	[ let PathStkTop = PathStk>>PS.PathStkTop
	let SP = lv PathStk>>PS.PSE↑PathStkTop
	if SP>>PSE.ESLFront eq Empty then return

	let Zone = Tree>>TREE.Zone

	ComputeEntLens(IS, SP)

	if PathStkTop le 0 then

		[ MakeNewRoot(IS, SP, Tree)
		return
		]

	let CurPtr = WriteBTreePage(Tree, SP>>PSE.PageNo)
	LockBTreePtr(Tree, lv CurPtr)

	let FreeWords = CurPtr>>BTP.FreeWords
	let Offset = SP>>PSE.Offset
	let TailBlkPtr = CurPtr+Offset
	let TailBlkLen = (IS>>IS.MaxFreeWords-FreeWords)-
		(Offset-Rec1Offset)

	let NewWords = PageLength(IS, 0)
	if NewWords le FreeWords then

		// All entries fit the current page. Hurrah!

		[ let T = Allocate(Zone, IS>>IS.MaxFreeWords)
		MoveBlock(T, TailBlkPtr, TailBlkLen)

		unless DepositESL(Tree, SP, TailBlkPtr, 
				NewWords) eq NewWords
			do BTreeBug(Tree, ecDepositESLWrong)

		MoveBlock(TailBlkPtr+NewWords, T, TailBlkLen)
		Free(Zone, T)

		CurPtr>>BTP.FreeWords = FreeWords-NewWords
		UnlockBTreePtr(Tree, lv CurPtr)
		return
		]

//	Not all the entries will fit on the current page. This is
//	getting complex.

	let LeftESEnt = MakeEntSeqEnt(Zone,
		 CurPtr+Rec1Offset, Offset-Rec1Offset)
	PushEntSeqEnt(SP, LeftESEnt)
	PushEntSeqEntLens(IS, LeftESEnt)

	let RightESEnt = MakeEntSeqEnt(Zone, TailBlkPtr, TailBlkLen)
	AppendEntSeqEnt(SP, RightESEnt)
	AppendEntSeqEntLens(IS, RightESEnt)

	UnlockBTreePtr(Tree, lv CurPtr)

	let RtBroPg1 = ComplexInsertRecords(Tree, PathStk, IS)

	if RtBroPg1 ne Empty then HairyInsertRecords(Tree, PathStk, IS,
		RtBroPg1)
	]


and ComputeEntLens(IS, PSE) be

	[ IS>>IS.ELLLen = 0
	(IS>>IS.ELLPtr)>>ELL.CumEntLen↑0 = 0
	let NextToScan = PSE>>PSE.ESLFront

	while NextToScan ne Empty do
		[ AppendEntSeqEntLens(IS, NextToScan)
		NextToScan = NextToScan>>ESLE.FwdP
		]
	]


and AppendEntSeqEntLens(IS, EntSeqEnt) be

	[ let LR = (IS>>IS.Tree)>>TREE.LengthRtn
	let CurSize = IS>>IS.ELLLen
	let EntLens = IS>>IS.ELLPtr

	let CumLen = EntLens>>ELL.CumEntLen↑CurSize
	let WordsLeft = EntSeqEnt>>ESLE.EntSeqLen
	let BTEntry = EntSeqEnt>>ESLE.EntSeqP

	while WordsLeft gr 0 do
		[ CurSize = CurSize+1

		let LengthThisEntry =
			(offset BTE.Record/16)+
			LR(BTEntry+(offset BTE.Record/16))
		CumLen = CumLen+LengthThisEntry
		EntLens>>ELL.CumEntLen↑CurSize = CumLen

		BTEntry = BTEntry+LengthThisEntry
		WordsLeft = WordsLeft-LengthThisEntry
		]

	IS>>IS.ELLLen = CurSize
	]


and PageLength(IS, LeftFather, RightFather; numargs na) = valof

	[ if na ls 3 then RightFather = IS>>IS.ELLLen+1
	let EntLens = IS>>IS.ELLPtr
	resultis (EntLens>>ELL.CumEntLen↑(RightFather-1)-
		EntLens>>ELL.CumEntLen↑LeftFather)
	]


and DepositESL(Tree, PSE, Block, Length) = valof

	[ let LR = Tree>>TREE.LengthRtn

	let OrigLength = Length

	while PSE>>PSE.ESLFront ne Empty do

		[ let ESLFront = PSE>>PSE.ESLFront
		let ESP = ESLFront>>ESLE.EntSeqP
		let ESLen = ESLFront>>ESLE.EntSeqLen

		test ESLen le Length

		ifso	[ MoveBlock(Block, ESP, ESLen)
			Block = Block+ESLen
			Length = Length-ESLen

			PSE>>PSE.ESLFront = ESLFront>>ESLE.FwdP
			Free(Tree>>TREE.Zone, ESLFront)
			]

		ifnot	[ let FirstRecP = lv (ESP>>BTE.Record)
			let FirstEntLen = LR(FirstRecP)+
					(offset BTE.Record/16)

			test FirstEntLen le Length

			ifso	[ MoveBlock(Block, ESP, FirstEntLen)
				Block = Block+FirstEntLen
				Length = Length-FirstEntLen

				ESLFront>>ESLE.EntSeqP =
					ESP+FirstEntLen
				ESLFront>>ESLE.EntSeqLen =
					ESLen-FirstEntLen
				]

			ifnot	resultis OrigLength-Length
			]
		]

	PSE>>PSE.ESLRear = Empty
	resultis OrigLength-Length
	]